From 6f82985e4aebb073486a3b330c8c003a0d318059 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 1 Dec 2022 15:39:50 +0200 Subject: [PATCH 01/20] initial commit --- .../passes/synthesis/high_level_synthesis.py | 79 +++++++++++++++++-- .../transpiler/preset_passmanagers/common.py | 6 +- setup.py | 2 + 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 168139d42333..c0f6914c8bcc 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -19,9 +19,10 @@ from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.transpiler.exceptions import TranspilerError from qiskit.quantum_info import decompose_clifford -from qiskit.synthesis.linear import synth_cnot_count_full_pmh +from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin - +from qiskit.transpiler import CouplingMap +import rustworkx as rx class HLSConfig: """The high-level-synthesis config allows to specify a list of "methods" used by @@ -89,9 +90,25 @@ class HighLevelSynthesis(TransformationPass): ``default`` methods for all other high-level objects, including ``op_a``-objects. """ - def __init__(self, hls_config=None): + def __init__( + self, + coupling_map: CouplingMap=None, + hls_config=None + ): + """ + HighLevelSynthesis initializer. + + Args: + coupling_map (CouplingMap): the coupling map of the backend + in case synthesis is done on a physical circuit. + hls_config (HLSConfig): the high-level-synthesis config file + specifying synthesis methods and parameters. + """ super().__init__() + print(f"HLS::init {coupling_map = }") + self._coupling_map = coupling_map + if hls_config is not None: self.hls_config = hls_config else: @@ -110,7 +127,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Raises: TranspilerError: when the specified synthesis method is not available. """ - + print(f"HLS::run {self._coupling_map = }") hls_plugin_manager = HighLevelSynthesisPluginManager() for node in dag.op_nodes(): @@ -141,8 +158,9 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: plugin_method = hls_plugin_manager.method(node.name, plugin_name) - # ToDo: similarly to UnitarySynthesis, we should pass additional parameters - # e.g. coupling_map to the synthesis algorithm. + if self._coupling_map: + plugin_args["coupling_map"] = self._coupling_map + decomposition = plugin_method.run(node.op, **plugin_args) # The synthesis methods that are not suited for the given higher-level-object @@ -159,6 +177,8 @@ class DefaultSynthesisClifford(HighLevelSynthesisPlugin): def run(self, high_level_object, **options): """Run synthesis for the given Clifford.""" + print(f"DefaultSynthesisClifford {options = }") + decomposition = decompose_clifford(high_level_object) return decomposition @@ -168,5 +188,52 @@ class DefaultSynthesisLinearFunction(HighLevelSynthesisPlugin): def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" + print(f"DefaultSynthesisLinearFunction {options = }") + decomposition = synth_cnot_count_full_pmh(high_level_object.linear) return decomposition + + +class KMSSynthesisLinearFunction(HighLevelSynthesisPlugin): + """Linear function synthesis plugin based on the Kutin-Moulton-Smithline method.""" + + def run(self, high_level_object, **options): + """Run synthesis for the given LinearFunction.""" + print(f"KMSSynthesisLinearFunction {options = }") + coupling_map = options.get("coupling_map", None) + + if coupling_map: + longest_path = _get_longest_line(coupling_map) + else: + longest_path = list(range()) + + # The KMS algorithm + if len(longest_path) < len(high_level_object.linear): + return None + + print(f"{coupling_map = }") + print(f"{longest_path = }") + + decomposition = synth_cnot_depth_line_kms(high_level_object.linear) + return decomposition + + +class PMHSynthesisLinearFunction(HighLevelSynthesisPlugin): + """Linear function synthesis plugin based on the Patel-Markov-Hayes method.""" + + def run(self, high_level_object, **options): + """Run synthesis for the given LinearFunction.""" + print(f"PMHSynthesisLinearFunction {options = }") + + decomposition = synth_cnot_count_full_pmh(high_level_object.linear) + return decomposition + + +def _get_longest_line(coupling_map: CouplingMap) -> list: + """Gets the longest line from the coupling map.""" + graph = coupling_map.graph + # longest_path = rx.longest_simple_path(graph) + simple_paths_generator = (y.values() for y in rx.all_pairs_all_simple_paths(graph).values()) + all_simple_paths = [x[0] for y in simple_paths_generator for x in y] + longest_path = max(all_simple_paths, key=len) + return list(longest_path) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 8766b526ee72..7afa87fa1ead 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -385,7 +385,7 @@ def generate_translation_passmanager( method=unitary_synthesis_method, target=target, ), - HighLevelSynthesis(hls_config=hls_config), + HighLevelSynthesis(coupling_map=coupling_map, hls_config=hls_config), UnrollCustomDefinitions(sel, basis_gates=basis_gates, target=target), BasisTranslator(sel, basis_gates, target), ] @@ -403,7 +403,7 @@ def generate_translation_passmanager( min_qubits=3, target=target, ), - HighLevelSynthesis(hls_config=hls_config), + HighLevelSynthesis(coupling_map=coupling_map, hls_config=hls_config), Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), Collect1qRuns(), @@ -417,7 +417,7 @@ def generate_translation_passmanager( method=unitary_synthesis_method, target=target, ), - HighLevelSynthesis(hls_config=hls_config), + HighLevelSynthesis(coupling_map=coupling_map, hls_config=hls_config), ] else: raise TranspilerError("Invalid translation method %s." % method) diff --git a/setup.py b/setup.py index ebeb6653a3af..83e2284b3d3a 100755 --- a/setup.py +++ b/setup.py @@ -105,6 +105,8 @@ "qiskit.synthesis": [ "clifford.default = qiskit.transpiler.passes.synthesis.high_level_synthesis:DefaultSynthesisClifford", "linear_function.default = qiskit.transpiler.passes.synthesis.high_level_synthesis:DefaultSynthesisLinearFunction", + "linear_function.kms = qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisLinearFunction", + "linear_function.pmh = qiskit.transpiler.passes.synthesis.high_level_synthesis:PMHSynthesisLinearFunction", ], "qiskit.transpiler.routing": [ "basic = qiskit.transpiler.preset_passmanagers.builtin_plugins:BasicSwapPassManager", From 646ca0d56f9a042201a5e365079e589a06ba690f Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 5 Dec 2022 17:12:21 +0200 Subject: [PATCH 02/20] Adding functionality to linear functions LNN plugin --- .../generalized_gates/linear_function.py | 12 ++ .../synthesis/linear/linear_circuits_utils.py | 81 +++++++++- .../passes/synthesis/high_level_synthesis.py | 145 +++++++++++++++--- 3 files changed, 207 insertions(+), 31 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 29c26b004f91..df927cab2426 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -171,6 +171,18 @@ def permutation_pattern(self): locs = np.where(linear == 1) return locs[1] + def permute(self, perm): + """Returns a linear function obtained by permuting rows and columns of a given linear function + using the permutation pattern ``perm``. + """ + mat = self.linear + nq = mat.shape[0] + permuted_mat = np.zeros(mat.shape, dtype=bool) + for i in range(nq): + for j in range(nq): + permuted_mat[i, j] = mat[perm[i], perm[j]] + return LinearFunction(permuted_mat) + def _linear_quantum_circuit_to_mat(qc: QuantumCircuit): """This creates a n x n matrix corresponding to the given linear quantum circuit.""" diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index 375c91e9e35f..ddf65a8ad79f 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -13,7 +13,7 @@ """Utility functions for handling linear reversible circuits.""" import copy -from typing import Callable +from typing import Callable, List import numpy as np from qiskit import QuantumCircuit from qiskit.exceptions import QiskitError @@ -61,10 +61,25 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b if not check_invertible_binary_matrix(mat): raise QiskitError("The matrix is not invertible.") + circuits = _cx_circuits_4_options(function, mat) + best_qc = choose_best_circuit(circuits, optimize_count) + return best_qc + + +def _cx_circuits_4_options(function: Callable, mat: np.ndarray) -> List[QuantumCircuit]: + """Construct different circuits implementing a binary invertible matrix M, + by considering all four options: M,M^(-1),M^T,M^(-1)^T. + + Args: + function: the synthesis function. + mat: a binary invertible matrix. + + Returns: + List[QuantumCircuit]: constructed circuits. + """ + circuits = [] qc = function(mat) - best_qc = qc - best_depth = qc.depth() - best_count = qc.count_ops()["cx"] + circuits.append(qc) for i in range(1, 4): mat_cpy = copy.deepcopy(mat) @@ -73,18 +88,42 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b mat_cpy = calc_inverse_matrix(mat_cpy) qc = function(mat_cpy) qc = qc.inverse() + circuits.append(qc) elif i == 2: mat_cpy = np.transpose(mat_cpy) qc = function(mat_cpy) qc = transpose_cx_circ(qc) + circuits.append(qc) elif i == 3: mat_cpy = calc_inverse_matrix(np.transpose(mat_cpy)) qc = function(mat_cpy) qc = transpose_cx_circ(qc) qc = qc.inverse() + circuits.append(qc) + + return circuits + + +def choose_best_circuit(circuits: List[QuantumCircuit], optimize_count: bool = True) -> QuantumCircuit: + """Returns the best quantum circuit either in terms of gate count or depth. + + Args: + circuits: a list of quantum circuits + optimize_count: True if the number of CX gates is optimized, False if the depth is optimized. + + Returns: + QuantumCircuit: the best quantum circuit out of the given circuits. + """ + best_qc = circuits[0] + best_depth = circuits[0].depth() + best_count = circuits[0].size() + print(f"In choose_best_circuit: count = {best_count}, depth = {best_depth}") + + for circuit in circuits[1:]: + new_depth = circuit.depth() + new_count = circuit.size() + print(f"In choose_best_circuit: count = {new_count}, depth = {new_depth}") - new_depth = qc.depth() - new_count = qc.count_ops()["cx"] # Prioritize count, and if it has the same count, then also consider depth better_count = (optimize_count and best_count > new_count) or ( not optimize_count and best_depth == new_depth and best_count > new_count @@ -97,6 +136,34 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b if better_count or better_depth: best_count = new_count best_depth = new_depth - best_qc = qc + best_qc = circuit return best_qc + + +def _compare_circuits(qc1: QuantumCircuit, qc2: QuantumCircuit, optimize_count: bool = True) -> bool: + """Compares two quantum circuits either in terms of gate count or depth. + + Args: + qc1: the first quantum circuit + qc2: the second quantum circuit + optimize_count: True if the number of CX gates is optimized, False if the depth is optimized. + + Returns: + bool: ``False`` means that the first quantum circuit is "better", ``True`` means the second. + """ + count1 = qc1.size() + depth1 = qc1.depth() + count2 = qc2.size() + depth2 = qc2.depth() + + # Prioritize count, and if it has the same count, then also consider depth + count2_is_better = (optimize_count and count1 > count2) or ( + not optimize_count and depth1 == depth2 and count1 > count2 + ) + # Prioritize depth, and if it has the same depth, then also consider count + depth2_is_better = (not optimize_count and depth1 > depth2) or ( + optimize_count and count1 == count2 and depth1 > depth2 + ) + + return count2_is_better or depth2_is_better diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index c0f6914c8bcc..9a7485552eed 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -20,9 +20,10 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.quantum_info import decompose_clifford from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms +from qiskit.synthesis.linear.linear_circuits_utils import optimize_cx_4_options, _compare_circuits from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin from qiskit.transpiler import CouplingMap -import rustworkx as rx + class HLSConfig: """The high-level-synthesis config allows to specify a list of "methods" used by @@ -127,9 +128,13 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Raises: TranspilerError: when the specified synthesis method is not available. """ - print(f"HLS::run {self._coupling_map = }") + print(f"HLS::run! {self._coupling_map = }") hls_plugin_manager = HighLevelSynthesisPluginManager() + dag_bit_indices = ( + {bit: i for i, bit in enumerate(dag.qubits)} + ) + for node in dag.op_nodes(): if node.name in self.hls_config.methods.keys(): @@ -160,14 +165,21 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if self._coupling_map: plugin_args["coupling_map"] = self._coupling_map + plugin_args["qubits"] = [dag_bit_indices[x] for x in node.qargs] decomposition = plugin_method.run(node.op, **plugin_args) - # The synthesis methods that are not suited for the given higher-level-object - # will return None, in which case the next method in the list will be used. + # The above synthesis method may return: + # - None, if the synthesis algorithm is not suited for the given higher-level-object. + # - decomposition when the order of qubits is not important + # - a tuple (decomposition, wires) when the node's qubits need to be reordered if decomposition is not None: - dag.substitute_node_with_dag(node, circuit_to_dag(decomposition)) - break + if isinstance(decomposition, tuple): + decomposition_dag = circuit_to_dag(decomposition[0]) + wires = [decomposition_dag.wires[i] for i in decomposition[1]] + dag.substitute_node_with_dag(node, decomposition_dag, wires=wires) + else: + dag.substitute_node_with_dag(node, circuit_to_dag(decomposition)) return dag @@ -190,6 +202,10 @@ def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" print(f"DefaultSynthesisLinearFunction {options = }") + coupling_map = options.get("coupling_map", None) + qubits = options.get("qubits", None) + + decomposition = synth_cnot_count_full_pmh(high_level_object.linear) return decomposition @@ -200,22 +216,71 @@ class KMSSynthesisLinearFunction(HighLevelSynthesisPlugin): def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" print(f"KMSSynthesisLinearFunction {options = }") + coupling_map = options.get("coupling_map", None) + qubits = options.get("qubits", None) + consider_all_mats = options.get("all_mats", 0) + consider_all_paths = options.get("all_paths", 0) + + + print(f"{coupling_map = }") + print(f"{qubits = }") + print(f"{consider_all_mats = }") + print(f"{consider_all_paths = }") + + # If not none, represents the path of qubits through the coupling map + # over which the LNN synthesis is applied. + best_path = None + + if not coupling_map: + if not consider_all_mats: + best_decomposition = synth_cnot_depth_line_kms(high_level_object.linear) + else: + best_decomposition = optimize_cx_4_options(synth_cnot_depth_line_kms, high_level_object.linear, optimize_count=False) - if coupling_map: - longest_path = _get_longest_line(coupling_map) else: - longest_path = list(range()) + best_decomposition = high_level_object.original_circuit - # The KMS algorithm - if len(longest_path) < len(high_level_object.linear): - return None + if best_decomposition: + print(f"HAVE INITIAL count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") + best_decomposition = None + print(coupling_map.size()) + print(coupling_map.get_edges()) - print(f"{coupling_map = }") - print(f"{longest_path = }") + reduced_map = coupling_map.reduce(qubits) - decomposition = synth_cnot_depth_line_kms(high_level_object.linear) - return decomposition + print(reduced_map.size()) + print(reduced_map.get_edges()) + + considered_paths = [] + if consider_all_paths: + all_paths = _all_hamiltonian_paths(reduced_map) + if all_paths: + considered_paths = all_paths + else: + path = _hamiltonian_path(reduced_map) + if path: + considered_paths = [path] + + print(f"PRINTING CONSIDERED PATHS: {considered_paths}") + for path in considered_paths: + print(f"PATH {path}") + permuted_linear_function = high_level_object.permute(path) + + if not consider_all_mats: + decomposition = synth_cnot_depth_line_kms(permuted_linear_function.linear) + else: + decomposition = optimize_cx_4_options(synth_cnot_depth_line_kms, permuted_linear_function.linear, optimize_count=False) + + if not best_decomposition or _compare_circuits(best_decomposition, decomposition, optimize_count=False): + best_decomposition = decomposition + best_path = path + print(f"IMPROVED! to count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") + + if best_path is None: + return best_decomposition + + return best_decomposition, best_path class PMHSynthesisLinearFunction(HighLevelSynthesisPlugin): @@ -229,11 +294,43 @@ def run(self, high_level_object, **options): return decomposition -def _get_longest_line(coupling_map: CouplingMap) -> list: - """Gets the longest line from the coupling map.""" - graph = coupling_map.graph - # longest_path = rx.longest_simple_path(graph) - simple_paths_generator = (y.values() for y in rx.all_pairs_all_simple_paths(graph).values()) - all_simple_paths = [x[0] for y in simple_paths_generator for x in y] - longest_path = max(all_simple_paths, key=len) - return list(longest_path) +def _hamiltonian_path(coupling_map: CouplingMap): + """Returns a Hamiltonian path (when exists) or None (when does not).""" + + def _recurse(current_node): + current_path.append(current_node) + if len(current_path) == coupling_map.size(): + return True + unvisited_neighbors = [node for node in coupling_map.neighbors(current_node) if node not in current_path] + for node in unvisited_neighbors: + if _recurse(node): + return True + current_path.pop() + return False + + current_path = [] + qubits = coupling_map.physical_qubits + for qubit in qubits: + if _recurse(qubit): + return current_path + return None + + +def _all_hamiltonian_paths(coupling_map: CouplingMap): + """Returns all Hamiltonian paths (when exist) or None (when does not).""" + + def _recurse(current_node): + current_path.append(current_node) + if len(current_path) == coupling_map.size(): + all_paths.append(current_path.copy()) + unvisited_neighbors = [node for node in coupling_map.neighbors(current_node) if node not in current_path] + for node in unvisited_neighbors: + _recurse(node) + current_path.pop() + + all_paths = [] + current_path = [] + qubits = coupling_map.physical_qubits + for qubit in qubits: + _recurse(qubit) + return all_paths From 57b1c7b01b44295a1c8048f146e3038a78ff887c Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 6 Dec 2022 14:28:19 +0200 Subject: [PATCH 03/20] Futher improvements --- .../synthesis/linear/linear_circuits_utils.py | 27 +--- .../passes/synthesis/high_level_synthesis.py | 118 ++++++++++++------ 2 files changed, 87 insertions(+), 58 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index ddf65a8ad79f..885b752cfc8b 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -62,7 +62,7 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b raise QiskitError("The matrix is not invertible.") circuits = _cx_circuits_4_options(function, mat) - best_qc = choose_best_circuit(circuits, optimize_count) + best_qc = _choose_best_circuit(circuits, optimize_count) return best_qc @@ -104,7 +104,7 @@ def _cx_circuits_4_options(function: Callable, mat: np.ndarray) -> List[QuantumC return circuits -def choose_best_circuit(circuits: List[QuantumCircuit], optimize_count: bool = True) -> QuantumCircuit: +def _choose_best_circuit(circuits: List[QuantumCircuit], optimize_count: bool = True) -> QuantumCircuit: """Returns the best quantum circuit either in terms of gate count or depth. Args: @@ -115,29 +115,9 @@ def choose_best_circuit(circuits: List[QuantumCircuit], optimize_count: bool = T QuantumCircuit: the best quantum circuit out of the given circuits. """ best_qc = circuits[0] - best_depth = circuits[0].depth() - best_count = circuits[0].size() - print(f"In choose_best_circuit: count = {best_count}, depth = {best_depth}") - for circuit in circuits[1:]: - new_depth = circuit.depth() - new_count = circuit.size() - print(f"In choose_best_circuit: count = {new_count}, depth = {new_depth}") - - # Prioritize count, and if it has the same count, then also consider depth - better_count = (optimize_count and best_count > new_count) or ( - not optimize_count and best_depth == new_depth and best_count > new_count - ) - # Prioritize depth, and if it has the same depth, then also consider count - better_depth = (not optimize_count and best_depth > new_depth) or ( - optimize_count and best_count == new_count and best_depth > new_depth - ) - - if better_count or better_depth: - best_count = new_count - best_depth = new_depth + if _compare_circuits(best_qc, circuit, optimize_count=optimize_count): best_qc = circuit - return best_qc @@ -152,6 +132,7 @@ def _compare_circuits(qc1: QuantumCircuit, qc2: QuantumCircuit, optimize_count: Returns: bool: ``False`` means that the first quantum circuit is "better", ``True`` means the second. """ + # TODO: this is not fully correct if there are SWAP gates count1 = qc1.size() depth1 = qc1.depth() count2 = qc2.size() diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 9a7485552eed..668fa30d9097 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -13,6 +13,7 @@ """Synthesize higher-level objects.""" +from typing import Union, List from qiskit.converters import circuit_to_dag from qiskit.transpiler.basepasses import TransformationPass @@ -202,11 +203,8 @@ def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" print(f"DefaultSynthesisLinearFunction {options = }") - coupling_map = options.get("coupling_map", None) - qubits = options.get("qubits", None) - + decomposition = PMHSynthesisLinearFunction().run(high_level_object, **options) - decomposition = synth_cnot_count_full_pmh(high_level_object.linear) return decomposition @@ -220,47 +218,47 @@ def run(self, high_level_object, **options): coupling_map = options.get("coupling_map", None) qubits = options.get("qubits", None) consider_all_mats = options.get("all_mats", 0) - consider_all_paths = options.get("all_paths", 0) - + max_paths = options.get("max_paths", 1) + consider_original_circuit = options.get("orig_circuit", 1) + optimize_count = options.get("opt_count", 1) print(f"{coupling_map = }") print(f"{qubits = }") print(f"{consider_all_mats = }") - print(f"{consider_all_paths = }") + print(f"{max_paths = }") + print(f"{consider_original_circuit = }") + print(f"{optimize_count = }") + + # At the end, if not none, represents the best decomposition adhering to LNN architecture. + best_decomposition = None - # If not none, represents the path of qubits through the coupling map + # At the end, if not none, represents the path of qubits through the coupling map # over which the LNN synthesis is applied. best_path = None + if consider_original_circuit: + best_decomposition = high_level_object.original_circuit + + if best_decomposition: + print(f"HAVE INITIAL count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") + if not coupling_map: if not consider_all_mats: - best_decomposition = synth_cnot_depth_line_kms(high_level_object.linear) + decomposition = synth_cnot_depth_line_kms(high_level_object.linear) else: - best_decomposition = optimize_cx_4_options(synth_cnot_depth_line_kms, high_level_object.linear, optimize_count=False) - - else: - best_decomposition = high_level_object.original_circuit + decomposition = optimize_cx_4_options(synth_cnot_depth_line_kms, high_level_object.linear, optimize_count=optimize_count) + print(f"NEW DECOMPOSITION count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") - if best_decomposition: - print(f"HAVE INITIAL count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") - best_decomposition = None - print(coupling_map.size()) - print(coupling_map.get_edges()) + if not best_decomposition or _compare_circuits(best_decomposition, decomposition, optimize_count=optimize_count): + best_decomposition = decomposition + print(f"IMPROVED!") + else: + # Consider the coupling map over the qubits on which the linear function is applied. reduced_map = coupling_map.reduce(qubits) - print(reduced_map.size()) - print(reduced_map.get_edges()) - - considered_paths = [] - if consider_all_paths: - all_paths = _all_hamiltonian_paths(reduced_map) - if all_paths: - considered_paths = all_paths - else: - path = _hamiltonian_path(reduced_map) - if path: - considered_paths = [path] + # Find one or more paths through the coupling map (when such exist). + considered_paths = _hamiltonian_paths(reduced_map, max_paths) print(f"PRINTING CONSIDERED PATHS: {considered_paths}") for path in considered_paths: @@ -271,11 +269,12 @@ def run(self, high_level_object, **options): decomposition = synth_cnot_depth_line_kms(permuted_linear_function.linear) else: decomposition = optimize_cx_4_options(synth_cnot_depth_line_kms, permuted_linear_function.linear, optimize_count=False) + print(f"NEW DECOMPOSITION count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") if not best_decomposition or _compare_circuits(best_decomposition, decomposition, optimize_count=False): best_decomposition = decomposition best_path = path - print(f"IMPROVED! to count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") + print(f"IMPROVED!") if best_path is None: return best_decomposition @@ -290,8 +289,38 @@ def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" print(f"PMHSynthesisLinearFunction {options = }") - decomposition = synth_cnot_count_full_pmh(high_level_object.linear) - return decomposition + coupling_map = options.get("coupling_map", None) + consider_all_mats = options.get("all_mats", 0) + consider_original_circuit = options.get("orig_circuit", 1) + optimize_count = options.get("opt_count", 1) + + # At the end, if not none, represents the best decomposition. + best_decomposition = None + + if consider_original_circuit: + best_decomposition = high_level_object.original_circuit + + if best_decomposition: + print(f"HAVE INITIAL count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") + + # This synthesis method is not aware of the coupling map, so we cannot apply + # this method when the coupling map is not None. + # (Though, technically, we could check if the reduced coupling map is + # fully-connected). + + print(f"I AM HERE, {coupling_map = }") + if not coupling_map: + if not consider_all_mats: + decomposition = synth_cnot_count_full_pmh(high_level_object.linear) + else: + decomposition = optimize_cx_4_options(synth_cnot_count_full_pmh, high_level_object.linear, optimize_count=optimize_count) + print(f"NEW DECOMPOSITION count = {decomposition.size()}, depth = {decomposition.depth()}") + + if not best_decomposition or _compare_circuits(best_decomposition, decomposition, optimize_count=optimize_count): + best_decomposition = decomposition + print(f"IMPROVED! to count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") + + return best_decomposition def _hamiltonian_path(coupling_map: CouplingMap): @@ -316,21 +345,40 @@ def _recurse(current_node): return None -def _all_hamiltonian_paths(coupling_map: CouplingMap): - """Returns all Hamiltonian paths (when exist) or None (when does not).""" +def _hamiltonian_paths(coupling_map: CouplingMap, cutoff: Union[None | int] = None) -> List[List[int]]: + """Returns a list of all Hamiltonian paths in ``coupling_map`` (stopping the enumeration when + the number of already discovered paths exceeds the ``cutoff`` value, when specified). + In particular, returns an empty list if there are no Hamiltonian paths. + """ + + # This is a temporary function, the plan is to move it to rustworkx + + def should_stop(): + return cutoff is not None and len(all_paths) >= cutoff def _recurse(current_node): current_path.append(current_node) if len(current_path) == coupling_map.size(): + # Discovered a new Hamiltonian path all_paths.append(current_path.copy()) + + if should_stop(): + return + unvisited_neighbors = [node for node in coupling_map.neighbors(current_node) if node not in current_path] for node in unvisited_neighbors: _recurse(node) + if should_stop(): + return + current_path.pop() all_paths = [] current_path = [] qubits = coupling_map.physical_qubits + for qubit in qubits: _recurse(qubit) + if should_stop(): + break return all_paths From 38a66a545a7040a49ffbe6568770c84576169ef3 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 6 Dec 2022 15:27:33 +0200 Subject: [PATCH 04/20] remove prints, black and lint --- .../synthesis/linear/linear_circuits_utils.py | 10 +- .../passes/synthesis/high_level_synthesis.py | 110 ++++++------------ 2 files changed, 45 insertions(+), 75 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index 885b752cfc8b..c2583c27c0d7 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -15,7 +15,7 @@ import copy from typing import Callable, List import numpy as np -from qiskit import QuantumCircuit +from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.circuit.exceptions import CircuitError from . import calc_inverse_matrix, check_invertible_binary_matrix @@ -104,7 +104,9 @@ def _cx_circuits_4_options(function: Callable, mat: np.ndarray) -> List[QuantumC return circuits -def _choose_best_circuit(circuits: List[QuantumCircuit], optimize_count: bool = True) -> QuantumCircuit: +def _choose_best_circuit( + circuits: List[QuantumCircuit], optimize_count: bool = True +) -> QuantumCircuit: """Returns the best quantum circuit either in terms of gate count or depth. Args: @@ -121,7 +123,9 @@ def _choose_best_circuit(circuits: List[QuantumCircuit], optimize_count: bool = return best_qc -def _compare_circuits(qc1: QuantumCircuit, qc2: QuantumCircuit, optimize_count: bool = True) -> bool: +def _compare_circuits( + qc1: QuantumCircuit, qc2: QuantumCircuit, optimize_count: bool = True +) -> bool: """Compares two quantum circuits either in terms of gate count or depth. Args: diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 668fa30d9097..ab5472a8fca7 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -19,11 +19,11 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.coupling import CouplingMap from qiskit.quantum_info import decompose_clifford from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms from qiskit.synthesis.linear.linear_circuits_utils import optimize_cx_4_options, _compare_circuits from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin -from qiskit.transpiler import CouplingMap class HLSConfig: @@ -92,11 +92,7 @@ class HighLevelSynthesis(TransformationPass): ``default`` methods for all other high-level objects, including ``op_a``-objects. """ - def __init__( - self, - coupling_map: CouplingMap=None, - hls_config=None - ): + def __init__(self, coupling_map: CouplingMap = None, hls_config=None): """ HighLevelSynthesis initializer. @@ -108,7 +104,6 @@ def __init__( """ super().__init__() - print(f"HLS::init {coupling_map = }") self._coupling_map = coupling_map if hls_config is not None: @@ -129,12 +124,9 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Raises: TranspilerError: when the specified synthesis method is not available. """ - print(f"HLS::run! {self._coupling_map = }") hls_plugin_manager = HighLevelSynthesisPluginManager() - dag_bit_indices = ( - {bit: i for i, bit in enumerate(dag.qubits)} - ) + dag_bit_indices = {bit: i for i, bit in enumerate(dag.qubits)} for node in dag.op_nodes(): @@ -190,8 +182,6 @@ class DefaultSynthesisClifford(HighLevelSynthesisPlugin): def run(self, high_level_object, **options): """Run synthesis for the given Clifford.""" - print(f"DefaultSynthesisClifford {options = }") - decomposition = decompose_clifford(high_level_object) return decomposition @@ -201,10 +191,8 @@ class DefaultSynthesisLinearFunction(HighLevelSynthesisPlugin): def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" - print(f"DefaultSynthesisLinearFunction {options = }") - + # For now, use PMH algorithm by default decomposition = PMHSynthesisLinearFunction().run(high_level_object, **options) - return decomposition @@ -213,8 +201,8 @@ class KMSSynthesisLinearFunction(HighLevelSynthesisPlugin): def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" - print(f"KMSSynthesisLinearFunction {options = }") + # options supported by this plugin coupling_map = options.get("coupling_map", None) qubits = options.get("qubits", None) consider_all_mats = options.get("all_mats", 0) @@ -222,13 +210,6 @@ def run(self, high_level_object, **options): consider_original_circuit = options.get("orig_circuit", 1) optimize_count = options.get("opt_count", 1) - print(f"{coupling_map = }") - print(f"{qubits = }") - print(f"{consider_all_mats = }") - print(f"{max_paths = }") - print(f"{consider_original_circuit = }") - print(f"{optimize_count = }") - # At the end, if not none, represents the best decomposition adhering to LNN architecture. best_decomposition = None @@ -239,19 +220,20 @@ def run(self, high_level_object, **options): if consider_original_circuit: best_decomposition = high_level_object.original_circuit - if best_decomposition: - print(f"HAVE INITIAL count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") - if not coupling_map: if not consider_all_mats: decomposition = synth_cnot_depth_line_kms(high_level_object.linear) else: - decomposition = optimize_cx_4_options(synth_cnot_depth_line_kms, high_level_object.linear, optimize_count=optimize_count) - print(f"NEW DECOMPOSITION count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") - - if not best_decomposition or _compare_circuits(best_decomposition, decomposition, optimize_count=optimize_count): + decomposition = optimize_cx_4_options( + synth_cnot_depth_line_kms, + high_level_object.linear, + optimize_count=optimize_count, + ) + + if not best_decomposition or _compare_circuits( + best_decomposition, decomposition, optimize_count=optimize_count + ): best_decomposition = decomposition - print(f"IMPROVED!") else: # Consider the coupling map over the qubits on which the linear function is applied. @@ -260,21 +242,23 @@ def run(self, high_level_object, **options): # Find one or more paths through the coupling map (when such exist). considered_paths = _hamiltonian_paths(reduced_map, max_paths) - print(f"PRINTING CONSIDERED PATHS: {considered_paths}") for path in considered_paths: - print(f"PATH {path}") permuted_linear_function = high_level_object.permute(path) if not consider_all_mats: decomposition = synth_cnot_depth_line_kms(permuted_linear_function.linear) else: - decomposition = optimize_cx_4_options(synth_cnot_depth_line_kms, permuted_linear_function.linear, optimize_count=False) - print(f"NEW DECOMPOSITION count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") + decomposition = optimize_cx_4_options( + synth_cnot_depth_line_kms, + permuted_linear_function.linear, + optimize_count=False, + ) - if not best_decomposition or _compare_circuits(best_decomposition, decomposition, optimize_count=False): + if not best_decomposition or _compare_circuits( + best_decomposition, decomposition, optimize_count=False + ): best_decomposition = decomposition best_path = path - print(f"IMPROVED!") if best_path is None: return best_decomposition @@ -287,8 +271,8 @@ class PMHSynthesisLinearFunction(HighLevelSynthesisPlugin): def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" - print(f"PMHSynthesisLinearFunction {options = }") + # options supported by this plugin coupling_map = options.get("coupling_map", None) consider_all_mats = options.get("all_mats", 0) consider_original_circuit = options.get("orig_circuit", 1) @@ -300,52 +284,32 @@ def run(self, high_level_object, **options): if consider_original_circuit: best_decomposition = high_level_object.original_circuit - if best_decomposition: - print(f"HAVE INITIAL count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") - # This synthesis method is not aware of the coupling map, so we cannot apply # this method when the coupling map is not None. # (Though, technically, we could check if the reduced coupling map is # fully-connected). - print(f"I AM HERE, {coupling_map = }") if not coupling_map: if not consider_all_mats: decomposition = synth_cnot_count_full_pmh(high_level_object.linear) else: - decomposition = optimize_cx_4_options(synth_cnot_count_full_pmh, high_level_object.linear, optimize_count=optimize_count) - print(f"NEW DECOMPOSITION count = {decomposition.size()}, depth = {decomposition.depth()}") - - if not best_decomposition or _compare_circuits(best_decomposition, decomposition, optimize_count=optimize_count): + decomposition = optimize_cx_4_options( + synth_cnot_count_full_pmh, + high_level_object.linear, + optimize_count=optimize_count, + ) + + if not best_decomposition or _compare_circuits( + best_decomposition, decomposition, optimize_count=optimize_count + ): best_decomposition = decomposition - print(f"IMPROVED! to count = {best_decomposition.size()}, depth = {best_decomposition.depth()}") return best_decomposition -def _hamiltonian_path(coupling_map: CouplingMap): - """Returns a Hamiltonian path (when exists) or None (when does not).""" - - def _recurse(current_node): - current_path.append(current_node) - if len(current_path) == coupling_map.size(): - return True - unvisited_neighbors = [node for node in coupling_map.neighbors(current_node) if node not in current_path] - for node in unvisited_neighbors: - if _recurse(node): - return True - current_path.pop() - return False - - current_path = [] - qubits = coupling_map.physical_qubits - for qubit in qubits: - if _recurse(qubit): - return current_path - return None - - -def _hamiltonian_paths(coupling_map: CouplingMap, cutoff: Union[None | int] = None) -> List[List[int]]: +def _hamiltonian_paths( + coupling_map: CouplingMap, cutoff: Union[None | int] = None +) -> List[List[int]]: """Returns a list of all Hamiltonian paths in ``coupling_map`` (stopping the enumeration when the number of already discovered paths exceeds the ``cutoff`` value, when specified). In particular, returns an empty list if there are no Hamiltonian paths. @@ -365,7 +329,9 @@ def _recurse(current_node): if should_stop(): return - unvisited_neighbors = [node for node in coupling_map.neighbors(current_node) if node not in current_path] + unvisited_neighbors = [ + node for node in coupling_map.neighbors(current_node) if node not in current_path + ] for node in unvisited_neighbors: _recurse(node) if should_stop(): From 2c6692c5ab5ec4888894d75015556345652be93f Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 6 Dec 2022 17:05:53 +0200 Subject: [PATCH 05/20] adding missing break --- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 9603cb9b30a1..36a3832a7f9a 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -174,6 +174,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: else: dag.substitute_node_with_dag(node, circuit_to_dag(decomposition)) + break + return dag From 243027ca4191b2835dd9e432aa03a266a734db01 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 7 Dec 2022 08:58:51 +0200 Subject: [PATCH 06/20] Adding an alternate way to specify HLS method --- .../passes/synthesis/high_level_synthesis.py | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 36a3832a7f9a..20d90b2b0a72 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -146,15 +146,25 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: methods = [] for method in methods: - plugin_name, plugin_args = method - if plugin_name not in hls_plugin_manager.method_names(node.name): - raise TranspilerError( - "Specified method: %s not found in available plugins for %s" - % (plugin_name, node.name) - ) + # There are two ways to specify an individual method being run, either a tuple + # ("kms", {"all_mats": 1, "max_paths": 100, "orig_circuit": 0}), + # or as a class instance + # KMSSynthesisLinearFunction(all_mats=1, max_paths=100, orig_circuit=0). + if isinstance(method, tuple): + plugin_name, plugin_args = method + + if plugin_name not in hls_plugin_manager.method_names(node.name): + raise TranspilerError( + "Specified method: %s not found in available plugins for %s" + % (plugin_name, node.name) + ) - plugin_method = hls_plugin_manager.method(node.name, plugin_name) + plugin_method = hls_plugin_manager.method(node.name, plugin_name) + + else: + plugin_method = method + plugin_args = {} if self._coupling_map: plugin_args["coupling_map"] = self._coupling_map @@ -163,7 +173,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: decomposition = plugin_method.run(node.op, **plugin_args) # The above synthesis method may return: - # - None, if the synthesis algorithm is not suited for the given higher-level-object. + # - None, if the synthesis algorithm is not suited for the given higher-level-object + # (in which case we consider the next method in the list if available). # - decomposition when the order of qubits is not important # - a tuple (decomposition, wires) when the node's qubits need to be reordered if decomposition is not None: @@ -201,16 +212,23 @@ def run(self, high_level_object, **options): class KMSSynthesisLinearFunction(HighLevelSynthesisPlugin): """Linear function synthesis plugin based on the Kutin-Moulton-Smithline method.""" + def __init__(self, **options): + self._options = options + def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" + # Combine the options passed in the initializer and now, + # prioritizing values passed now. + run_options = self._options | options + # options supported by this plugin - coupling_map = options.get("coupling_map", None) - qubits = options.get("qubits", None) - consider_all_mats = options.get("all_mats", 0) - max_paths = options.get("max_paths", 1) - consider_original_circuit = options.get("orig_circuit", 1) - optimize_count = options.get("opt_count", 1) + coupling_map = run_options.get("coupling_map", None) + qubits = run_options.get("qubits", None) + consider_all_mats = run_options.get("all_mats", 0) + max_paths = run_options.get("max_paths", 1) + consider_original_circuit = run_options.get("orig_circuit", 1) + optimize_count = run_options.get("opt_count", 1) # At the end, if not none, represents the best decomposition adhering to LNN architecture. best_decomposition = None @@ -271,14 +289,21 @@ def run(self, high_level_object, **options): class PMHSynthesisLinearFunction(HighLevelSynthesisPlugin): """Linear function synthesis plugin based on the Patel-Markov-Hayes method.""" + def __init__(self, **options): + self._options = options + def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" + # Combine the options passed in the initializer and now, + # prioritizing values passed now. + run_options = self._options | options + # options supported by this plugin - coupling_map = options.get("coupling_map", None) - consider_all_mats = options.get("all_mats", 0) - consider_original_circuit = options.get("orig_circuit", 1) - optimize_count = options.get("opt_count", 1) + coupling_map = run_options.get("coupling_map", None) + consider_all_mats = run_options.get("all_mats", 0) + consider_original_circuit = run_options.get("orig_circuit", 1) + optimize_count = run_options.get("opt_count", 1) # At the end, if not none, represents the best decomposition. best_decomposition = None @@ -310,7 +335,7 @@ def run(self, high_level_object, **options): def _hamiltonian_paths( - coupling_map: CouplingMap, cutoff: Union[None | int] = None + coupling_map: CouplingMap, cutoff: Union[None, int] = None ) -> List[List[int]]: """Returns a list of all Hamiltonian paths in ``coupling_map`` (stopping the enumeration when the number of already discovered paths exceeds the ``cutoff`` value, when specified). From a56056614a3a261dc1c91bea71c874d712d829fa Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 7 Dec 2022 09:08:23 +0200 Subject: [PATCH 07/20] improving performance of _hamiltonian_paths --- .../passes/synthesis/high_level_synthesis.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 20d90b2b0a72..542b373a6923 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -344,34 +344,38 @@ def _hamiltonian_paths( # This is a temporary function, the plan is to move it to rustworkx - def should_stop(): + def _should_stop(): return cutoff is not None and len(all_paths) >= cutoff def _recurse(current_node): current_path.append(current_node) + on_path[current_node] = True + if len(current_path) == coupling_map.size(): # Discovered a new Hamiltonian path all_paths.append(current_path.copy()) - if should_stop(): + if _should_stop(): return unvisited_neighbors = [ - node for node in coupling_map.neighbors(current_node) if node not in current_path + node for node in coupling_map.neighbors(current_node) if not on_path[node] ] for node in unvisited_neighbors: _recurse(node) - if should_stop(): + if _should_stop(): return current_path.pop() + on_path[current_node] = False all_paths = [] - current_path = [] qubits = coupling_map.physical_qubits + current_path = [] + on_path = [False] * len(qubits) for qubit in qubits: _recurse(qubit) - if should_stop(): + if _should_stop(): break return all_paths From 482450e10916a4756f0d96ff4bf884149f0289d9 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 7 Dec 2022 14:53:35 +0200 Subject: [PATCH 08/20] Adding tests for linear function synthesis plugins --- .../test_linear_functions_passes.py | 177 +++++++++++++++++- 1 file changed, 176 insertions(+), 1 deletion(-) diff --git a/test/python/transpiler/test_linear_functions_passes.py b/test/python/transpiler/test_linear_functions_passes.py index c6b383c31d5b..6463b5841e04 100644 --- a/test/python/transpiler/test_linear_functions_passes.py +++ b/test/python/transpiler/test_linear_functions_passes.py @@ -22,11 +22,17 @@ from qiskit.transpiler.passes.synthesis import ( LinearFunctionsSynthesis, LinearFunctionsToPermutations, + HighLevelSynthesis, + HLSConfig, +) +from qiskit.transpiler.passes.synthesis.high_level_synthesis import ( + PMHSynthesisLinearFunction, + KMSSynthesisLinearFunction, ) from qiskit.test import QiskitTestCase from qiskit.circuit.library.generalized_gates import LinearFunction from qiskit.circuit.library import RealAmplitudes -from qiskit.transpiler import PassManager +from qiskit.transpiler import PassManager, CouplingMap from qiskit.quantum_info import Operator @@ -555,6 +561,175 @@ def test_do_not_merge_conditional_gates(self): # Make sure that the condition on the middle gate is not lost self.assertIsNotNone(qct.data[1].operation.condition) + def test_synthesis_using_pmh(self): + """Test high level synthesis of linear functions using the PMHSynthesisLinearFunction + plugin. The synthesis method is specified as a tuple consisting of the method to run + and additional parameters. + """ + qc = QuantumCircuit(6) + mat = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] + linear_function = LinearFunction(mat) + qc.append(linear_function, [1, 2, 3, 4]) + + # Identifies plugin + config = HLSConfig(linear_function=[("pmh", {"all_mats": 1})]) + pm = PassManager(HighLevelSynthesis(hls_config=config)) + qct = pm.run(qc) + self.assertNotIn("linear_function", qct.count_ops().keys()) + + def test_synthesis_using_pmh_alternate_form(self): + """Test high level synthesis of linear functions using the PMHSynthesisLinearFunction + plugin. The synthesis method is specified as a class instance.""" + qc = QuantumCircuit(6) + mat = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] + linear_function = LinearFunction(mat) + qc.append(linear_function, [1, 2, 3, 4]) + + # Test that running plugin specifying class method works correctly + pmh = PMHSynthesisLinearFunction(all_mats=1) + config = HLSConfig(linear_function=[pmh]) + pm = PassManager(HighLevelSynthesis(hls_config=config)) + qct = pm.run(qc) + self.assertNotIn("linear_function", qct.count_ops().keys()) + + def test_synthesis_using_pmh_with_coupling_map(self): + """Test high level synthesis of linear functions using the PMHSynthesisLinearFunction + plugin when the coupling map is specified, in which case the linear function should + not be synthesized. + """ + qc = QuantumCircuit(6) + mat = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] + linear_function = LinearFunction(mat) + qc.append(linear_function, [1, 2, 3, 4]) + + coupling_map = CouplingMap.from_line(6) + config = HLSConfig(linear_function=[("pmh", {"all_mats": 1})]) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + qct = pm.run(qc) + self.assertIn("linear_function", qct.count_ops().keys()) + + def test_synthesis_using_pmh_with_original_circuit(self): + """Test high level synthesis of linear functions using the PMHSynthesisLinearFunction + plugin when the linear function is created from some linear circuit. If the coupling map + is specified and ``orig_circuit`` option is 0, the linear function should not be + synthesized. However, if the ``orig_circuit`` option is 1, the linear function should + be replaced by this linear circuit.""" + qcl = QuantumCircuit(4) + qcl.cx(0, 1) + qcl.cx(0, 2) + qcl.cx(0, 3) + linear_function = LinearFunction(qcl) + + qc = QuantumCircuit(6) + qc.append(linear_function, [1, 2, 3, 4]) + + # First, setting orig_circuit to 0 + coupling_map = CouplingMap.from_line(6) + config = HLSConfig(linear_function=[("pmh", {"orig_circuit": 0})]) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + qct = pm.run(qc) + self.assertIn("linear_function", qct.count_ops().keys()) + + # Second, setting orig_circuit to 1 + config = HLSConfig(linear_function=[("pmh", {"orig_circuit": 1})]) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + qct = pm.run(qc) + self.assertNotIn("linear_function", qct.count_ops().keys()) + + def test_synthesis_using_kms(self): + """Test high level synthesis of linear functions using the KMSSynthesisLinearFunction + plugin. The synthesis method is specified as a tuple consisting of the method to run + and additional parameters. + """ + qc = QuantumCircuit(6) + mat = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] + linear_function = LinearFunction(mat) + qc.append(linear_function, [1, 2, 3, 4]) + + # Identifies plugin + config = HLSConfig(linear_function=[("kms", {"all_mats": 1})]) + pm = PassManager(HighLevelSynthesis(hls_config=config)) + qct = pm.run(qc) + self.assertNotIn("linear_function", qct.count_ops().keys()) + + def test_synthesis_using_kms_with_coupling_map(self): + """Test high level synthesis of linear functions using the KMSSynthesisLinearFunction + plugin when the coupling map is specified. The linear function should be synthesized + if the coupling map restricted to the set of qubits over which the linear function + is defined contains a hamiltonian path. + """ + mat = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] + linear_function = LinearFunction(mat) + + coupling_map = CouplingMap([(0, 1), (1, 2), (1, 3), (4, 3), (3, 5)]) + coupling_map.make_symmetric() + + config = HLSConfig(linear_function=[("kms", {"orig_circuit": 0})]) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + + # First, consider the case that the linear function is defined over a set of qubits + # with a hamiltonian path + qc1 = QuantumCircuit(6) + qc1.append(linear_function, [1, 2, 3, 4]) + qct1 = pm.run(qc1) + self.assertNotIn("linear_function", qct1.count_ops().keys()) + + # Second, consider the case that the linear function is defined over a set of qubits + # without a Hamiltonian path + qc2 = QuantumCircuit(6) + qc2.append(linear_function, [1, 3, 4, 5]) + qct2 = pm.run(qc2) + self.assertIn("linear_function", qct2.count_ops().keys()) + + def test_synthesis_using_kms_with_original_circuit(self): + """Test high level synthesis of linear functions using the KMSSynthesisLinearFunction + plugin when the linear function is created from some linear circuit. If the coupling + map does not have a hamiltonian path but the ``orig_circuit`` option is 1, the linear + function should be synthesized.""" + qcl = QuantumCircuit(4) + qcl.cx(0, 1) + qcl.cx(0, 2) + qcl.cx(0, 3) + linear_function = LinearFunction(qcl) + + coupling_map = CouplingMap([(0, 1), (1, 2), (1, 3), (4, 3), (3, 5)]) + coupling_map.make_symmetric() + + qc = QuantumCircuit(6) + qc.append(linear_function, [1, 3, 4, 5]) + + # First, the case that synthesis does not apply and orig_circuit option is 0. + config = HLSConfig(linear_function=[("kms", {"orig_circuit": 0})]) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + qct = pm.run(qc) + self.assertIn("linear_function", qct.count_ops().keys()) + + # Second, the case that synthesis does not apply but orig_circuit option is 1. + config = HLSConfig(linear_function=[("kms", {"orig_circuit": 1})]) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + qct = pm.run(qc) + self.assertNotIn("linear_function", qct.count_ops().keys()) + + def test_synthesis_with_multiple_methods(self): + """Test high level synthesis with multiple methods.""" + mat = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] + linear_function = LinearFunction(mat) + qc = QuantumCircuit(6) + qc.append(linear_function, [1, 2, 3, 4]) + coupling_map = CouplingMap.from_line(6) + # This specifies a sequence of different methods to use (additionally, specified using + # different forms). Only the last method synthesizes the function. + config = HLSConfig( + linear_function=[ + ("pmh", {"orig_circuit": 0}), + PMHSynthesisLinearFunction(orig_circuit=1), + KMSSynthesisLinearFunction(orig_circuit=0), + ] + ) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + qct = pm.run(qc) + self.assertNotIn("linear_function", qct.count_ops().keys()) + if __name__ == "__main__": unittest.main() From 07e4881f8e90b94c7809e8804c390907c1553a06 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 7 Dec 2022 15:31:18 +0200 Subject: [PATCH 09/20] release notes --- ...-coupling-map-to-hls-f8d377ca98f5e1fc.yaml | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 releasenotes/notes/add-coupling-map-to-hls-f8d377ca98f5e1fc.yaml diff --git a/releasenotes/notes/add-coupling-map-to-hls-f8d377ca98f5e1fc.yaml b/releasenotes/notes/add-coupling-map-to-hls-f8d377ca98f5e1fc.yaml new file mode 100644 index 000000000000..1c7d1ef887f1 --- /dev/null +++ b/releasenotes/notes/add-coupling-map-to-hls-f8d377ca98f5e1fc.yaml @@ -0,0 +1,50 @@ +--- +features: + - | + Added the ability to pass the coupling map to :class:`.HighLevelSynthesis` + transpiler pass. When the coupling map is not ``None``, each synthesis method + (built on top of :class:`.HighLevelSynthesisPlugin`) is required to synthesize + a given high-level object adhering to this coupling map, or return ``None`` if + unable to do so. + - | + Added two high-level-synthesis plugins for :class:`.LinearFunction`. The + :class:`.PMHSynthesisLinearFunction` is the synthesis plugin built on top + of the Patel-Markov-Hayes method. As the underlying method does not take the + coupling map into account, the plugin only synthesizes the given linear function + when the coupling map is ``None``, and returns ``None`` otherwise. The + :class:`.KMSSynthesisLinearFunction` is the synthesis plugin built on top of the + Kutin-Moulton-Smithline method. The underlying synthesis method synthesizes with + respect to linear nearest-neighbour (LNN) architecture. The plugin synthesizes + the given linear function either when the coupling map is ``None``, or when there + exists a hamiltonian path through the qubits over which this linear function is + defined. When the coupling map is not ``None`` and the hamiltonian path does + not exist, this plugin returns ``None``. +upgrade: + - | + Added an alternative way to specify the synthesis methods used for a given + high-level-object in :class:`.HighLevelSynthesis` transpiler pass. Now, each + synthesis method can be either a tuple consisting of the name of the method + and additional arguments, or an instance of :class:`.HighLevelSynthesisPlugin`. + The following example shows how one can do both, even using both forms in the + list of synthesis methods:: + + from qiskit import QuantumCircuit + from qiskit.transpiler import PassManager, CouplingMap + from qiskit.transpiler.passes import HighLevelSynthesis + from qiskit.circuit.library.generalized_gates import LinearFunction + from qiskit.transpiler.passes.synthesis.high_level_synthesis import KMSSynthesisLinearFunction + + mat = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] + linear_function = LinearFunction(mat) + qc = QuantumCircuit(6) + qc.append(linear_function, [1, 2, 3, 4]) + coupling_map = CouplingMap.from_line(6) + + config = HLSConfig( + linear_function=[ + ("pmh", {"orig_circuit": 0}), + KMSSynthesisLinearFunction(orig_circuit=0), + ] + ) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + qc_synthesized = pm.run(qc) From 8363767b06ab6df3a4b236d688ef8b90edecf841 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 7 Dec 2022 16:04:30 +0200 Subject: [PATCH 10/20] taking union of two dicts that also works for python 3.7 --- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 542b373a6923..64d7e2d69e91 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -220,7 +220,8 @@ def run(self, high_level_object, **options): # Combine the options passed in the initializer and now, # prioritizing values passed now. - run_options = self._options | options + run_options = self._options.copy() + run_options.update(options) # options supported by this plugin coupling_map = run_options.get("coupling_map", None) @@ -297,7 +298,9 @@ def run(self, high_level_object, **options): # Combine the options passed in the initializer and now, # prioritizing values passed now. - run_options = self._options | options + # Note: run_options = self._options | options is only supported for python >= 3.9. + run_options = self._options.copy() + run_options.update(options) # options supported by this plugin coupling_map = run_options.get("coupling_map", None) From 33f7e5d06176b5705cb7371dd15c547ea5322976 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 21 Dec 2022 12:37:24 +0200 Subject: [PATCH 11/20] adding test for LinearFunction permute method --- .../circuit/library/test_linear_function.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index b7ca592ace80..42faf3d93179 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -239,6 +239,22 @@ def test_no_original_definition(self): linear_function = LinearFunction(mat) self.assertIsNone(linear_function.original_circuit) + def test_permute(self): + """Tests correctness of ``permute`` method.""" + mat = [[1, 1, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] + + # This means: 2->0, 0->1, 1->2, 3->3 + perm = [2, 0, 1, 3] + + permuted_matrix = LinearFunction(mat).permute(perm).linear.astype(int) + + # expected matrix is obtained by first reordering columns + # according to the permutation pattern, i.e. + # [[0, 1, 1, 0], [0, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 0]], + # and then reordering rows of the matrix. + expected_matrix = [[0, 0, 0, 1], [0, 1, 1, 0], [0, 0, 1, 0], [1, 0, 0, 0]] + self.assertTrue(np.all(permuted_matrix == expected_matrix)) + if __name__ == "__main__": unittest.main() From e044be1f87c724ad39cb1211e1f8092a76307013 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 21 Dec 2022 12:43:35 +0200 Subject: [PATCH 12/20] renaming optimize_cx_4_options to _optimize_cx_4_options --- qiskit/synthesis/linear/linear_circuits_utils.py | 2 +- .../passes/synthesis/high_level_synthesis.py | 8 ++++---- test/python/synthesis/test_linear_synthesis.py | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index c2583c27c0d7..b1922b1c50d9 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -42,7 +42,7 @@ def transpose_cx_circ(qc: QuantumCircuit): return transposed_circ -def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: bool = True): +def _optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: bool = True): """Get the best implementation of a circuit implementing a binary invertible matrix M, by considering all four options: M,M^(-1),M^T,M^(-1)^T. Optimizing either the CX count or the depth. diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 64d7e2d69e91..310a98ffb3bf 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -22,7 +22,7 @@ from qiskit.transpiler.coupling import CouplingMap from qiskit.synthesis.clifford import synth_clifford_full from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms -from qiskit.synthesis.linear.linear_circuits_utils import optimize_cx_4_options, _compare_circuits +from qiskit.synthesis.linear.linear_circuits_utils import _optimize_cx_4_options, _compare_circuits from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin @@ -245,7 +245,7 @@ def run(self, high_level_object, **options): if not consider_all_mats: decomposition = synth_cnot_depth_line_kms(high_level_object.linear) else: - decomposition = optimize_cx_4_options( + decomposition = _optimize_cx_4_options( synth_cnot_depth_line_kms, high_level_object.linear, optimize_count=optimize_count, @@ -269,7 +269,7 @@ def run(self, high_level_object, **options): if not consider_all_mats: decomposition = synth_cnot_depth_line_kms(permuted_linear_function.linear) else: - decomposition = optimize_cx_4_options( + decomposition = _optimize_cx_4_options( synth_cnot_depth_line_kms, permuted_linear_function.linear, optimize_count=False, @@ -323,7 +323,7 @@ def run(self, high_level_object, **options): if not consider_all_mats: decomposition = synth_cnot_count_full_pmh(high_level_object.linear) else: - decomposition = optimize_cx_4_options( + decomposition = _optimize_cx_4_options( synth_cnot_count_full_pmh, high_level_object.linear, optimize_count=optimize_count, diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index 0448414e6b70..4348a55e21eb 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -25,7 +25,7 @@ check_invertible_binary_matrix, calc_inverse_matrix, ) -from qiskit.synthesis.linear.linear_circuits_utils import transpose_cx_circ, optimize_cx_4_options +from qiskit.synthesis.linear.linear_circuits_utils import transpose_cx_circ, _optimize_cx_4_options from qiskit.test import QiskitTestCase @@ -43,7 +43,7 @@ def test_lnn_circuit(self): mat = LinearFunction(qc).linear for optimized in [True, False]: - optimized_qc = optimize_cx_4_options( + optimized_qc = _optimize_cx_4_options( synth_cnot_count_full_pmh, mat, optimize_count=optimized ) self.assertEqual(optimized_qc.depth(), 4) @@ -60,7 +60,7 @@ def test_full_circuit(self): mat = LinearFunction(qc).linear for optimized in [True, False]: - optimized_qc = optimize_cx_4_options( + optimized_qc = _optimize_cx_4_options( synth_cnot_count_full_pmh, mat, optimize_count=optimized ) self.assertEqual(optimized_qc.depth(), 4) @@ -94,11 +94,11 @@ def test_example_circuit(self): qc.cx(1, 0) mat = LinearFunction(qc).linear - optimized_qc = optimize_cx_4_options(synth_cnot_count_full_pmh, mat, optimize_count=True) + optimized_qc = _optimize_cx_4_options(synth_cnot_count_full_pmh, mat, optimize_count=True) self.assertEqual(optimized_qc.depth(), 17) self.assertEqual(optimized_qc.count_ops()["cx"], 20) - optimized_qc = optimize_cx_4_options(synth_cnot_count_full_pmh, mat, optimize_count=False) + optimized_qc = _optimize_cx_4_options(synth_cnot_count_full_pmh, mat, optimize_count=False) self.assertEqual(optimized_qc.depth(), 15) self.assertEqual(optimized_qc.count_ops()["cx"], 23) From a87d495bb08b05109652663dcb9d247d0d9d7aa0 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 21 Dec 2022 18:08:12 +0200 Subject: [PATCH 13/20] Adding documentation for two linear function synthesis plugin + bug fix --- .../synthesis/linear/linear_circuits_utils.py | 15 ++++ .../passes/synthesis/high_level_synthesis.py | 68 +++++++++++++++++-- .../test_linear_functions_passes.py | 37 ++++++++-- 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index b1922b1c50d9..a486387805a1 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -152,3 +152,18 @@ def _compare_circuits( ) return count2_is_better or depth2_is_better + + +def _check_coupling_map(qc: QuantumCircuit, coupling_list: list) -> bool: + """Returns whether a linear quantum circuit (consisting of CX and SWAP gates) + only has connections from the coupling_list.""" + coupling_list_set = set(coupling_list) + for circuit_instruction in qc.data: + qargs = circuit_instruction.qubits + if len(qargs) != 2: + return False + q0 = qc.find_bit(circuit_instruction.qubits[0]).index + q1 = qc.find_bit(circuit_instruction.qubits[1]).index + if (q0, q1) not in coupling_list_set: + return False + return True diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 310a98ffb3bf..e0e90c15ca26 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -22,7 +22,11 @@ from qiskit.transpiler.coupling import CouplingMap from qiskit.synthesis.clifford import synth_clifford_full from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms -from qiskit.synthesis.linear.linear_circuits_utils import _optimize_cx_4_options, _compare_circuits +from qiskit.synthesis.linear.linear_circuits_utils import ( + _optimize_cx_4_options, + _compare_circuits, + _check_coupling_map, +) from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin @@ -210,7 +214,33 @@ def run(self, high_level_object, **options): class KMSSynthesisLinearFunction(HighLevelSynthesisPlugin): - """Linear function synthesis plugin based on the Kutin-Moulton-Smithline method.""" + """Linear function synthesis plugin based on the Kutin-Moulton-Smithline method. + + The plugin also supports the following plugin-specific options that can be passed + in ``__init__`` and ``run`` methods: + + * coupling_map: CouplingMap of the target backend or ``None``. If this coupling map + is not ``None``, then the synthesized quantum circuit should adhere to the imposed + connectivity constraints. Note that the synthesis algorithm is allowed to return + ``None`` when it is unable to synthesize the desired circuit. + * qubits: the sequence of qubits over which the ``high_level_object`` is defined. + This is only used in the case that the coupling map is not ``None``. + * opt_count: an option to prioritize count over depth when multiple synthesized + circuits are available. + * all_mats: an option to synthesize quantum circuits for the matrix, its transpose, + its inverse, and its inverse transpose, choosing the best implementation. + * max_paths: when the coupling map is not ``None``, the internally used synthesis + algorithm needs to pick a hamiltonian path through ``qubits``. By default, only one + hamiltonian pass is considered. This option allows to increase the maximum number + of paths that should be considered, choosing the best implementation. + * orig_circuit: an option to also consider the original implementation of the linear + function when available, choosing the best implementation. Note that the original + implementation might not adhere to the coupling map. + + The output of the ``run`` method should either be ``None`` if the circuit could + not be synthesized, a ``QuantumCircuit``, or a pair consisting of a quantum circuit + and an ordered list of qubits. + """ def __init__(self, **options): self._options = options @@ -238,10 +268,10 @@ def run(self, high_level_object, **options): # over which the LNN synthesis is applied. best_path = None - if consider_original_circuit: - best_decomposition = high_level_object.original_circuit - if not coupling_map: + if consider_original_circuit: + best_decomposition = high_level_object.original_circuit + if not consider_all_mats: decomposition = synth_cnot_depth_line_kms(high_level_object.linear) else: @@ -260,6 +290,12 @@ def run(self, high_level_object, **options): # Consider the coupling map over the qubits on which the linear function is applied. reduced_map = coupling_map.reduce(qubits) + # We can consider the original definition if it's compliant with the reduced map. + if consider_original_circuit and _check_coupling_map( + high_level_object.original_circuit, reduced_map + ): + best_decomposition = high_level_object.original_circuit + # Find one or more paths through the coupling map (when such exist). considered_paths = _hamiltonian_paths(reduced_map, max_paths) @@ -288,7 +324,27 @@ def run(self, high_level_object, **options): class PMHSynthesisLinearFunction(HighLevelSynthesisPlugin): - """Linear function synthesis plugin based on the Patel-Markov-Hayes method.""" + """Linear function synthesis plugin based on the Patel-Markov-Hayes method. + + The plugin also supports the following plugin-specific options that can be passed + in ``__init__`` and ``run`` methods: + + * coupling_map: CouplingMap of the target backend or ``None``. If this coupling map + is not ``None``, then the synthesized quantum circuit should adhere to the imposed + connectivity constraints. Note that the synthesis algorithm is allowed to return + ``None`` when it is unable to synthesize the desired circuit. + * opt_count: an option to prioritize count over depth when multiple synthesized + circuits are available. + * all_mats: an option to synthesize quantum circuits for the matrix, its transpose, + its inverse, and its inverse transpose, choosing the best implementation. + * orig_circuit: an option to also consider the original implementation of the linear + function when available, choosing the best implementation. Note that the original + implementation might not adhere to the coupling map. + + The output of the ``run`` method should either be ``None`` if the circuit could + not be synthesized, a ``QuantumCircuit``, or a pair consisting of a quantum circuit + and an ordered list of qubits. + """ def __init__(self, **options): self._options = options diff --git a/test/python/transpiler/test_linear_functions_passes.py b/test/python/transpiler/test_linear_functions_passes.py index 6463b5841e04..427c786bbd8a 100644 --- a/test/python/transpiler/test_linear_functions_passes.py +++ b/test/python/transpiler/test_linear_functions_passes.py @@ -681,11 +681,11 @@ def test_synthesis_using_kms_with_coupling_map(self): qct2 = pm.run(qc2) self.assertIn("linear_function", qct2.count_ops().keys()) - def test_synthesis_using_kms_with_original_circuit(self): + def test_synthesis_using_kms_with_compliant_original_circuit(self): """Test high level synthesis of linear functions using the KMSSynthesisLinearFunction plugin when the linear function is created from some linear circuit. If the coupling - map does not have a hamiltonian path but the ``orig_circuit`` option is 1, the linear - function should be synthesized.""" + map does not have a hamiltonian path but the ``orig_circuit`` option is 1 and the linear + function is adheres to the coupling map, the linear function should be synthesized.""" qcl = QuantumCircuit(4) qcl.cx(0, 1) qcl.cx(0, 2) @@ -696,7 +696,7 @@ def test_synthesis_using_kms_with_original_circuit(self): coupling_map.make_symmetric() qc = QuantumCircuit(6) - qc.append(linear_function, [1, 3, 4, 5]) + qc.append(linear_function, [3, 1, 4, 5]) # First, the case that synthesis does not apply and orig_circuit option is 0. config = HLSConfig(linear_function=[("kms", {"orig_circuit": 0})]) @@ -710,6 +710,35 @@ def test_synthesis_using_kms_with_original_circuit(self): qct = pm.run(qc) self.assertNotIn("linear_function", qct.count_ops().keys()) + def test_synthesis_using_kms_with_non_compliant_original_circuit(self): + """Test high level synthesis of linear functions using the KMSSynthesisLinearFunction + plugin when the linear function is created from some linear circuit. If the coupling + map does not have a hamiltonian path but the ``orig_circuit`` option is 1 yet the linear + function does not adhere to the coupling map, the linear function should not be synthesized.""" + qcl = QuantumCircuit(4) + qcl.cx(0, 1) + qcl.cx(0, 2) + qcl.cx(0, 3) + linear_function = LinearFunction(qcl) + + coupling_map = CouplingMap([(0, 1), (1, 2), (1, 3), (4, 3), (3, 5)]) + coupling_map.make_symmetric() + + qc = QuantumCircuit(6) + qc.append(linear_function, [1, 3, 4, 5]) + + # First, the case that synthesis does not apply and orig_circuit option is 0. + config = HLSConfig(linear_function=[("kms", {"orig_circuit": 0})]) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + qct = pm.run(qc) + self.assertIn("linear_function", qct.count_ops().keys()) + + # Second, the case that synthesis does not apply but orig_circuit option is 1. + config = HLSConfig(linear_function=[("kms", {"orig_circuit": 1})]) + pm = PassManager(HighLevelSynthesis(hls_config=config, coupling_map=coupling_map)) + qct = pm.run(qc) + self.assertIn("linear_function", qct.count_ops().keys()) + def test_synthesis_with_multiple_methods(self): """Test high level synthesis with multiple methods.""" mat = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 1, 1]] From 6a1ab8d8a36df1265221552256330baf4484f2c0 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 22 Dec 2022 14:28:20 +0200 Subject: [PATCH 14/20] Fixing depth computation of linear circuits, renaming auxiliary functions --- .../synthesis/linear/linear_circuits_utils.py | 48 +++++++++++++------ .../passes/synthesis/high_level_synthesis.py | 12 ++--- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index a486387805a1..212cb248f2a9 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -18,7 +18,10 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.circuit.exceptions import CircuitError -from . import calc_inverse_matrix, check_invertible_binary_matrix +from qiskit.synthesis.linear.linear_matrix_utils import ( + calc_inverse_matrix, + check_invertible_binary_matrix, +) def transpose_cx_circ(qc: QuantumCircuit): @@ -62,7 +65,7 @@ def _optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: raise QiskitError("The matrix is not invertible.") circuits = _cx_circuits_4_options(function, mat) - best_qc = _choose_best_circuit(circuits, optimize_count) + best_qc = _choose_best_linear_circuit(circuits, optimize_count) return best_qc @@ -104,7 +107,7 @@ def _cx_circuits_4_options(function: Callable, mat: np.ndarray) -> List[QuantumC return circuits -def _choose_best_circuit( +def _choose_best_linear_circuit( circuits: List[QuantumCircuit], optimize_count: bool = True ) -> QuantumCircuit: """Returns the best quantum circuit either in terms of gate count or depth. @@ -118,12 +121,29 @@ def _choose_best_circuit( """ best_qc = circuits[0] for circuit in circuits[1:]: - if _compare_circuits(best_qc, circuit, optimize_count=optimize_count): + if _compare_linear_circuits(best_qc, circuit, optimize_count=optimize_count): best_qc = circuit return best_qc -def _compare_circuits( +def _linear_circuit_depth(qc: QuantumCircuit): + """Computes the depth of a linear circuit (that is, a circuit that only contains CX and SWAP + gates). This is similar to quantum circuit's depth method except that SWAPs are counted as + depth-3 gates. + """ + qubit_depths = [0] * qc.num_qubits + bit_indices = {bit: idx for idx, bit in enumerate(qc.qubits)} + for instruction in qc.data: + if instruction.operation.name not in ["cx", "swap"]: + raise CircuitError("The circuit contains non-linear gates.") + new_depth = max(qubit_depths[bit_indices[q]] for q in instruction.qubits) + new_depth += 3 if instruction.operation.name == "swap" else 1 + for q in instruction.qubits: + qubit_depths[bit_indices[q]] = new_depth + return max(qubit_depths) + + +def _compare_linear_circuits( qc1: QuantumCircuit, qc2: QuantumCircuit, optimize_count: bool = True ) -> bool: """Compares two quantum circuits either in terms of gate count or depth. @@ -136,11 +156,10 @@ def _compare_circuits( Returns: bool: ``False`` means that the first quantum circuit is "better", ``True`` means the second. """ - # TODO: this is not fully correct if there are SWAP gates count1 = qc1.size() - depth1 = qc1.depth() + depth1 = _linear_circuit_depth(qc1) count2 = qc2.size() - depth2 = qc2.depth() + depth2 = _linear_circuit_depth(qc2) # Prioritize count, and if it has the same count, then also consider depth count2_is_better = (optimize_count and count1 > count2) or ( @@ -154,16 +173,17 @@ def _compare_circuits( return count2_is_better or depth2_is_better -def _check_coupling_map(qc: QuantumCircuit, coupling_list: list) -> bool: +def _linear_circuit_complies_with_coupling_map(qc: QuantumCircuit, coupling_list: list) -> bool: """Returns whether a linear quantum circuit (consisting of CX and SWAP gates) - only has connections from the coupling_list.""" + only has connections from the coupling_list. + """ + bit_indices = {bit: idx for idx, bit in enumerate(qc.qubits)} coupling_list_set = set(coupling_list) for circuit_instruction in qc.data: - qargs = circuit_instruction.qubits - if len(qargs) != 2: + if len(circuit_instruction.qubits) != 2: return False - q0 = qc.find_bit(circuit_instruction.qubits[0]).index - q1 = qc.find_bit(circuit_instruction.qubits[1]).index + q0 = bit_indices[circuit_instruction.qubits[0]] + q1 = bit_indices[circuit_instruction.qubits[1]] if (q0, q1) not in coupling_list_set: return False return True diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index e0e90c15ca26..b6532f43b113 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -24,8 +24,8 @@ from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms from qiskit.synthesis.linear.linear_circuits_utils import ( _optimize_cx_4_options, - _compare_circuits, - _check_coupling_map, + _compare_linear_circuits, + _linear_circuit_complies_with_coupling_map, ) from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin @@ -281,7 +281,7 @@ def run(self, high_level_object, **options): optimize_count=optimize_count, ) - if not best_decomposition or _compare_circuits( + if not best_decomposition or _compare_linear_circuits( best_decomposition, decomposition, optimize_count=optimize_count ): best_decomposition = decomposition @@ -291,7 +291,7 @@ def run(self, high_level_object, **options): reduced_map = coupling_map.reduce(qubits) # We can consider the original definition if it's compliant with the reduced map. - if consider_original_circuit and _check_coupling_map( + if consider_original_circuit and _linear_circuit_complies_with_coupling_map( high_level_object.original_circuit, reduced_map ): best_decomposition = high_level_object.original_circuit @@ -311,7 +311,7 @@ def run(self, high_level_object, **options): optimize_count=False, ) - if not best_decomposition or _compare_circuits( + if not best_decomposition or _compare_linear_circuits( best_decomposition, decomposition, optimize_count=False ): best_decomposition = decomposition @@ -385,7 +385,7 @@ def run(self, high_level_object, **options): optimize_count=optimize_count, ) - if not best_decomposition or _compare_circuits( + if not best_decomposition or _compare_linear_circuits( best_decomposition, decomposition, optimize_count=optimize_count ): best_decomposition = decomposition From af6658f6aa7b083c6fdc5713adaf32b894e7c7c7 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 22 Dec 2022 15:22:40 +0200 Subject: [PATCH 15/20] renaming for lint --- qiskit/synthesis/linear/linear_circuits_utils.py | 2 +- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index 212cb248f2a9..a68720d6d004 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -173,7 +173,7 @@ def _compare_linear_circuits( return count2_is_better or depth2_is_better -def _linear_circuit_complies_with_coupling_map(qc: QuantumCircuit, coupling_list: list) -> bool: +def _linear_circuit_check_map(qc: QuantumCircuit, coupling_list: list) -> bool: """Returns whether a linear quantum circuit (consisting of CX and SWAP gates) only has connections from the coupling_list. """ diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index b6532f43b113..d41dcdf8a87b 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -25,7 +25,7 @@ from qiskit.synthesis.linear.linear_circuits_utils import ( _optimize_cx_4_options, _compare_linear_circuits, - _linear_circuit_complies_with_coupling_map, + _linear_circuit_check_map, ) from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin @@ -291,7 +291,7 @@ def run(self, high_level_object, **options): reduced_map = coupling_map.reduce(qubits) # We can consider the original definition if it's compliant with the reduced map. - if consider_original_circuit and _linear_circuit_complies_with_coupling_map( + if consider_original_circuit and _linear_circuit_check_map( high_level_object.original_circuit, reduced_map ): best_decomposition = high_level_object.original_circuit From a3242535bd04b7cf1395e7624940c283491de09b Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 10 Jan 2023 18:29:28 +0200 Subject: [PATCH 16/20] Adding tests as per review comments --- .../python/synthesis/test_linear_synthesis.py | 79 ++++++++++++++++++- .../test_linear_functions_passes.py | 9 +++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index 4348a55e21eb..590bbcfb1534 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -25,7 +25,13 @@ check_invertible_binary_matrix, calc_inverse_matrix, ) -from qiskit.synthesis.linear.linear_circuits_utils import transpose_cx_circ, _optimize_cx_4_options +from qiskit.synthesis.linear.linear_circuits_utils import ( + transpose_cx_circ, + _optimize_cx_4_options, + _linear_circuit_depth, + _compare_linear_circuits, + _linear_circuit_check_map, +) from qiskit.test import QiskitTestCase @@ -136,6 +142,77 @@ def test_synth_lnn_kms(self, num_qubits): dist = abs(q0 - q1) self.assertEqual(dist, 1) + def test_linear_circuit_depth(self): + """Test the ``_linear_circuit_depth`` utility method, in particular that it + returns the same depth when SWAPs are replaced by three CXs.""" + + qc1 = QuantumCircuit(4) + qc1.cx(0, 1) + qc1.cx(0, 2) + qc1.swap(1, 3) + qc1.cx(0, 3) + + qc2 = QuantumCircuit(4) + qc2.cx(0, 1) + qc2.cx(0, 2) + qc2.cx(1, 3) + qc2.cx(3, 1) + qc2.cx(1, 3) + qc2.cx(0, 3) + + depth1 = _linear_circuit_depth(qc1) + depth2 = _linear_circuit_depth(qc2) + + self.assertEqual(depth1, depth2) + + def test_compare_linear_circuits_count(self): + """Test the ``_compare_linear_circuits`` utility method.""" + + # Create two quantum circuits, one with better count, another with better depth + # (ignoring the fact that they do not implement the same linear function). + qc1 = QuantumCircuit(4) + qc1.cx(0, 1) + qc1.cx(2, 3) + qc1.cx(0, 2) + qc1.cx(1, 3) + self.assertEqual(qc1.size(), 4) + self.assertEqual(qc1.depth(), 2) + + qc2 = QuantumCircuit(4) + qc2.cx(0, 1) + qc2.cx(0, 2) + qc2.cx(0, 3) + self.assertEqual(qc2.size(), 3) + self.assertEqual(qc2.depth(), 3) + + qc2_count_is_better = _compare_linear_circuits(qc1, qc2, optimize_count=True) + qc2_depth_is_better = _compare_linear_circuits(qc1, qc2, optimize_count=False) + + self.assertTrue(qc2_count_is_better) + self.assertFalse(qc2_depth_is_better) + + def test_linear_circuit_check_map(self): + """Test the ``_linear_circuit_check_map`` utility method.""" + coupling_list = [(0, 1), (1, 2), (2, 3)] + + qc1 = QuantumCircuit(4) + qc1.cx(1, 2) + qc1.cx(2, 3) + qc1.cx(1, 2) + self.assertTrue(_linear_circuit_check_map(qc1, coupling_list)) + + qc2 = QuantumCircuit(4) + qc2.cx(1, 2) + qc2.cx(1, 3) + qc2.cx(1, 2) + self.assertFalse(_linear_circuit_check_map(qc2, coupling_list)) + + qc3 = QuantumCircuit(4) + qc3.cx(1, 2) + qc3.cx(3, 2) + qc3.cx(1, 2) + self.assertFalse(_linear_circuit_check_map(qc3, coupling_list)) + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_linear_functions_passes.py b/test/python/transpiler/test_linear_functions_passes.py index 427c786bbd8a..14eaa3046a6c 100644 --- a/test/python/transpiler/test_linear_functions_passes.py +++ b/test/python/transpiler/test_linear_functions_passes.py @@ -28,6 +28,7 @@ from qiskit.transpiler.passes.synthesis.high_level_synthesis import ( PMHSynthesisLinearFunction, KMSSynthesisLinearFunction, + _hamiltonian_paths, ) from qiskit.test import QiskitTestCase from qiskit.circuit.library.generalized_gates import LinearFunction @@ -759,6 +760,14 @@ def test_synthesis_with_multiple_methods(self): qct = pm.run(qc) self.assertNotIn("linear_function", qct.count_ops().keys()) + def test_hamiltonian_paths(self): + """Test the utility ``_hamiltonian_paths`` method.""" + coupling_list = [(0, 1), (1, 2), (2, 3), (1, 3), (2, 0)] + computed_paths = _hamiltonian_paths(CouplingMap(coupling_list)) + computed_paths_set = {tuple(path) for path in computed_paths} + expected_paths_set = {tuple([0, 1, 2, 3]), tuple([2, 0, 1, 3])} + self.assertEqual(computed_paths_set, expected_paths_set) + if __name__ == "__main__": unittest.main() From 13cbc6e7cbf11d70cd43fdc0ebafb83f066f3f76 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 4 Apr 2023 11:34:07 +0300 Subject: [PATCH 17/20] fixing the order of arguments and adding Target --- .../passes/synthesis/high_level_synthesis.py | 14 ++++++++++---- qiskit/transpiler/preset_passmanagers/common.py | 9 +++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 8a56961efdc1..bc48f0a6ab98 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -29,6 +29,7 @@ _linear_circuit_check_map, ) from qiskit.synthesis.permutation import synth_permutation_depth_lnn_kms +from qiskit.transpiler.target import Target from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin @@ -114,19 +115,24 @@ class HighLevelSynthesis(TransformationPass): ``default`` methods for all other high-level objects, including ``op_a``-objects. """ - def __init__(self, coupling_map: CouplingMap = None, hls_config=None): + def __init__(self, hls_config=None, coupling_map=None, target=None): """ HighLevelSynthesis initializer. Args: + hls_config (HLSConfig): the high-level-synthesis config file + specifying synthesis methods and parameters. coupling_map (CouplingMap): the coupling map of the backend in case synthesis is done on a physical circuit. - hls_config (HLSConfig): the high-level-synthesis config file - specifying synthesis methods and parameters. + target (Target): A target representing the target backend. """ super().__init__() self._coupling_map = coupling_map + self._target = target + if target is not None: + self._coupling_map = self._target.build_coupling_map() + print(f"HLS: {self._coupling_map = }") if hls_config is not None: self.hls_config = hls_config @@ -194,7 +200,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: plugin_method = self.hls_plugin_manager.method(node.name, plugin_specifier) else: plugin_method = plugin_specifier - + if self._coupling_map: plugin_args["coupling_map"] = self._coupling_map plugin_args["qubits"] = [dag_bit_indices[x] for x in node.qargs] diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 3a3b3bcf4891..69b9ed4f9ed7 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -190,7 +190,8 @@ def generate_unroll_3q( target=target, ) ) - unroll_3q.append(HighLevelSynthesis(hls_config=hls_config)) + print(f"generate_unroll_3q: {target = }") + unroll_3q.append(HighLevelSynthesis(hls_config=hls_config, target=target)) unroll_3q.append(Unroll3qOrMore(target=target, basis_gates=basis_gates)) return unroll_3q @@ -384,7 +385,7 @@ def generate_translation_passmanager( method=unitary_synthesis_method, target=target, ), - HighLevelSynthesis(coupling_map=coupling_map, hls_config=hls_config), + HighLevelSynthesis(hls_config=hls_config, coupling_map=coupling_map, target=target), UnrollCustomDefinitions(sel, basis_gates=basis_gates, target=target), BasisTranslator(sel, basis_gates, target), ] @@ -402,7 +403,7 @@ def generate_translation_passmanager( min_qubits=3, target=target, ), - HighLevelSynthesis(coupling_map=coupling_map, hls_config=hls_config), + HighLevelSynthesis(hls_config=hls_config, coupling_map=coupling_map, target=target), Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), Collect1qRuns(), @@ -418,7 +419,7 @@ def generate_translation_passmanager( method=unitary_synthesis_method, target=target, ), - HighLevelSynthesis(coupling_map=coupling_map, hls_config=hls_config), + HighLevelSynthesis(hls_config=hls_config, coupling_map=coupling_map, target=target), ] else: raise TranspilerError("Invalid translation method %s." % method) From 0e9b95ae9f5a8f3b58843d46be076b2d5e9dad7b Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 5 Apr 2023 12:14:10 +0300 Subject: [PATCH 18/20] fix --- qiskit/synthesis/linear/linear_circuits_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index c85bcb1a8165..a4d232c0237f 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -186,6 +186,8 @@ def _linear_circuit_check_map(qc: QuantumCircuit, coupling_list: list) -> bool: q1 = bit_indices[circuit_instruction.qubits[1]] if (q0, q1) not in coupling_list_set: return False + return True + def check_lnn_connectivity(qc: QuantumCircuit) -> bool: """Check that the synthesized circuit qc fits linear nearest neighbor connectivity. From c5599dfa317a1574c08159ba8eb6652a19cf2a03 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 5 Apr 2023 12:14:34 +0300 Subject: [PATCH 19/20] remove print statement --- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index bc48f0a6ab98..09e02f12263f 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -132,7 +132,6 @@ def __init__(self, hls_config=None, coupling_map=None, target=None): self._target = target if target is not None: self._coupling_map = self._target.build_coupling_map() - print(f"HLS: {self._coupling_map = }") if hls_config is not None: self.hls_config = hls_config From 4e1b1e08a3b7b61126b33515131a997f19784568 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 5 Apr 2023 12:35:42 +0300 Subject: [PATCH 20/20] remove print statement --- qiskit/transpiler/preset_passmanagers/common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 69b9ed4f9ed7..07810890d521 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -190,7 +190,6 @@ def generate_unroll_3q( target=target, ) ) - print(f"generate_unroll_3q: {target = }") unroll_3q.append(HighLevelSynthesis(hls_config=hls_config, target=target)) unroll_3q.append(Unroll3qOrMore(target=target, basis_gates=basis_gates)) return unroll_3q