From 7bf50cb5d0c54c199c43d75b0ca8bd1e3290b4cf Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 17 Aug 2022 12:32:12 +0300 Subject: [PATCH 01/39] move graysynth from transpiler.synthesis to synthesis.linear --- .../library/generalized_gates/linear_function.py | 2 +- qiskit/synthesis/linear/__init__.py | 16 ++++++++++++++++ .../synthesis => synthesis/linear}/graysynth.py | 0 qiskit/transpiler/synthesis/__init__.py | 3 --- test/python/transpiler/test_synthesis.py | 10 ++++++++-- 5 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 qiskit/synthesis/linear/__init__.py rename qiskit/{transpiler/synthesis => synthesis/linear}/graysynth.py (100%) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 15ab05ce426b..f86471c12893 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -134,7 +134,7 @@ def synthesize(self): Returns: QuantumCircuit: A circuit implementing the evolution. """ - from qiskit.transpiler.synthesis import cnot_synth + from qiskit.synthesis.linear import cnot_synth return cnot_synth(self.linear) diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py new file mode 100644 index 000000000000..5321f8cebe44 --- /dev/null +++ b/qiskit/synthesis/linear/__init__.py @@ -0,0 +1,16 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Module containing cnot circuits and cnot-phase circuit synthesize.""" + + +from .graysynth import graysynth, cnot_synth diff --git a/qiskit/transpiler/synthesis/graysynth.py b/qiskit/synthesis/linear/graysynth.py similarity index 100% rename from qiskit/transpiler/synthesis/graysynth.py rename to qiskit/synthesis/linear/graysynth.py diff --git a/qiskit/transpiler/synthesis/__init__.py b/qiskit/transpiler/synthesis/__init__.py index 575daf2625b0..bf4e7e4888a5 100644 --- a/qiskit/transpiler/synthesis/__init__.py +++ b/qiskit/transpiler/synthesis/__init__.py @@ -11,6 +11,3 @@ # that they have been altered from the originals. """Module containing transpiler synthesize.""" - - -from .graysynth import graysynth, cnot_synth diff --git a/test/python/transpiler/test_synthesis.py b/test/python/transpiler/test_synthesis.py index 1f7fb297d5a4..cd413d7fba29 100644 --- a/test/python/transpiler/test_synthesis.py +++ b/test/python/transpiler/test_synthesis.py @@ -10,12 +10,14 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test synthesis algorithms""" +"""Test cnot circuit and cnot-phase circuit synthesis algorithms""" + +import unittest from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.quantum_info.operators import Operator from qiskit.extensions.unitary import UnitaryGate -from qiskit.transpiler.synthesis import graysynth, cnot_synth +from qiskit.synthesis.linear import graysynth, cnot_synth from qiskit.test import QiskitTestCase @@ -262,3 +264,7 @@ def test_patel_markov_hayes(self): # Check if the two circuits are equivalent self.assertEqual(unitary_patel, unitary_compare) + + +if __name__ == "__main__": + unittest.main() From 0fc0946e240d683bb228924a62c42725f24ea934 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 17 Aug 2022 14:19:08 +0300 Subject: [PATCH 02/39] add linear utilities for binary matrices: inverse matrix, random matrix --- qiskit/synthesis/linear/__init__.py | 1 + qiskit/synthesis/linear/linear_utils.py | 137 ++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 qiskit/synthesis/linear/linear_utils.py diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index 5321f8cebe44..17e11804bdb7 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -14,3 +14,4 @@ from .graysynth import graysynth, cnot_synth +from .linear_utils import random_invertible_binary_matrix, calc_inverse_matrix diff --git a/qiskit/synthesis/linear/linear_utils.py b/qiskit/synthesis/linear/linear_utils.py new file mode 100644 index 000000000000..662e475fb307 --- /dev/null +++ b/qiskit/synthesis/linear/linear_utils.py @@ -0,0 +1,137 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Utility functions for handling binary matrices.""" + +import numpy as np +from qiskit.exceptions import QiskitError + + +def _calc_rank(A): + # This function takes as input a binary square matrix A + # Returns the rank of A over the binary field F_2 + n = A.shape[1] + X = np.identity(n, dtype=int) + + for i in range(n): + y = np.dot(A[i, :], X) % 2 + not_y = (y + 1) % 2 + good = X[:, np.nonzero(not_y)] + good = good[:, 0, :] + bad = X[:, np.nonzero(y)] + bad = bad[:, 0, :] + if bad.shape[1] > 0: + bad = np.add(bad, np.roll(bad, 1, axis=1)) + bad = bad % 2 + bad = np.delete(bad, 0, axis=1) + X = np.concatenate((good, bad), axis=1) + # now columns of X span the binary null-space of A + return n - X.shape[1] + + +def random_invertible_binary_matrix(num_qubits, seed=None): + """Generates a random invertible n x n binary matrix.""" + # This code is adapted from random_cnotdihedral + if isinstance(seed, np.random.Generator): + rng = seed + else: + rng = np.random.default_rng(seed) + + rank = 0 + while rank != num_qubits: + mat = rng.integers(2, size=(num_qubits, num_qubits)) + rank = _calc_rank(mat) + return mat + + +def _gauss_elimination(A, ncols=None, full_elim=False): + """Gauss elimination of a matrix A with m rows and n columns. + If full_elim = True, it allows full elimination of A[:, 0 : ncols] + Returns the matrix A and the permutation perm that was done on the rows + during the process.""" + + # Treat the matrix A as containing integer values + A = np.array(A, dtype=int, copy=False) + + m = A.shape[0] # no. of rows + n = A.shape[1] # no. of columns + if ncols is not None: + n = min(n, ncols) # no. of active columns + + perm = np.array(range(n)) # permutation on the rows + + r = 0 # current rank + k = 0 # current pivot column + while (r < m) and (k < n): + isNZ = False + new_r = r + for j in range(k, n): + for i in range(r, m): + if A[i][j]: + isNZ = True + k = j + new_r = i + break + if isNZ: + break + if not isNZ: + return A, perm # A is in the canonical form + + if new_r != r: + tmp = A[new_r].copy() + A[new_r] = A[r] + A[r] = tmp + perm[r], perm[new_r] = perm[new_r], perm[r] + + if full_elim: + for i in range(0, r): + if A[i][k]: + A[i] = A[i] ^ A[r] + + for i in range(r + 1, m): + if A[i][k]: + A[i] = A[i] ^ A[r] + r += 1 + + return A, perm + + +def calc_inverse_matrix(A, verify=False): + """Given a square numpy(dtype=int) matrix A, tries to compute its inverse. + Returns None if the matrix is not invertible, and the inverse otherwise.""" + + if A.shape[0] != A.shape[1]: + raise QiskitError("Matrix to invert is a non-square matrix.") + + n = A.shape[0] + # concatenate the matrix and identity + B = np.concatenate((A, np.eye(n, dtype=int)), axis=1) + B, _ = _gauss_elimination(B, None, full_elim=True) + + r = _compute_rank_after_gauss_elim(B[:, 0:n]) + + if r < n: + raise QiskitError("The matrix is not invertible") + + else: + C = B[:, n : 2 * n] + + if verify: + D = np.dot(A, C) % 2 + assert np.array_equal(D, np.eye(n)) + return C + + +def _compute_rank_after_gauss_elim(A): + """Given a matrix A after Gaussian elimination, computes its rank + (i.e. simply the number of nonzero rows)""" + return np.sum(A.any(axis=1)) From 8fa1ba2eb83dd955b1bc01a9e56158fc304b1cce Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 17 Aug 2022 14:19:51 +0300 Subject: [PATCH 03/39] update random invertible binary matrix in random_cnotdihedral --- qiskit/quantum_info/operators/dihedral/random.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qiskit/quantum_info/operators/dihedral/random.py b/qiskit/quantum_info/operators/dihedral/random.py index 6ba2a354f4fd..91ae7575e665 100644 --- a/qiskit/quantum_info/operators/dihedral/random.py +++ b/qiskit/quantum_info/operators/dihedral/random.py @@ -48,10 +48,8 @@ def random_cnotdihedral(num_qubits, seed=None): # Random affine function # Random invertible binary matrix - det = 0 - while np.allclose(det, 0) or np.allclose(det, 2): - linear = rng.integers(2, size=(num_qubits, num_qubits)) - det = np.linalg.det(linear) % 2 + from qiskit.synthesis.linear import random_invertible_binary_matrix + linear = random_invertible_binary_matrix(num_qubits, seed=rng) elem.linear = linear # Random shift From 61fdce5f72d95834cba2b71b2c316d89640f0468 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 17 Aug 2022 14:22:58 +0300 Subject: [PATCH 04/39] update random invertible binary matrix in test_linear_function --- .../circuit/library/test_linear_function.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index ee96fe6ba70d..61f77f7425c6 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -22,6 +22,7 @@ from qiskit.circuit.library.standard_gates import CXGate, SwapGate from qiskit.circuit.library.generalized_gates import LinearFunction from qiskit.circuit.exceptions import CircuitError +from qiskit.synthesis.linear import random_invertible_binary_matrix from qiskit.quantum_info.operators import Operator @@ -51,23 +52,6 @@ def random_linear_circuit(num_qubits, num_gates, seed=None): return circ -def random_invertible_binary_matrix(num_qubits, seed=None): - """Generates a random invertible n x n binary matrix.""" - - # This code is adapted from random_cnotdihedral - if isinstance(seed, np.random.Generator): - rng = seed - else: - rng = np.random.default_rng(seed) - - det = 0 - while np.allclose(det, 0) or np.allclose(det, 2): - binary_matrix = rng.integers(2, size=(num_qubits, num_qubits)) - det = np.linalg.det(binary_matrix) % 2 - - return binary_matrix - - @ddt class TestLinearFunctions(QiskitTestCase): """Tests for clifford append gate functions.""" From 0309580a04a442d3b9c6f63f8dd345dfb31f3496 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 17 Aug 2022 14:24:13 +0300 Subject: [PATCH 05/39] fix deprecation warning in graysynth --- qiskit/synthesis/linear/graysynth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/linear/graysynth.py b/qiskit/synthesis/linear/graysynth.py index 0f68c26ef8a0..32ecfa1ecd4f 100644 --- a/qiskit/synthesis/linear/graysynth.py +++ b/qiskit/synthesis/linear/graysynth.py @@ -176,7 +176,7 @@ def graysynth(cnots, angles, section_size=2): else: sta.append([cnots1, list(set(ilist).difference([j])), qubit]) sta.append([cnots0, list(set(ilist).difference([j])), qubit]) - qcir += cnot_synth(state, section_size).inverse() + qcir = qcir.compose(cnot_synth(state, section_size).inverse()) return qcir From f6a0735d42420362196eed2711de19b29586c7a4 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 17 Aug 2022 15:09:41 +0300 Subject: [PATCH 06/39] style --- qiskit/quantum_info/operators/dihedral/random.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/quantum_info/operators/dihedral/random.py b/qiskit/quantum_info/operators/dihedral/random.py index 91ae7575e665..d531eb9a43c6 100644 --- a/qiskit/quantum_info/operators/dihedral/random.py +++ b/qiskit/quantum_info/operators/dihedral/random.py @@ -49,6 +49,7 @@ def random_cnotdihedral(num_qubits, seed=None): # Random affine function # Random invertible binary matrix from qiskit.synthesis.linear import random_invertible_binary_matrix + linear = random_invertible_binary_matrix(num_qubits, seed=rng) elem.linear = linear From b0f7fbac5c772c81892c93e511c49728d2360dbe Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 17 Aug 2022 16:21:39 +0300 Subject: [PATCH 07/39] update variable names --- qiskit/synthesis/linear/linear_utils.py | 92 ++++++++++++------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/qiskit/synthesis/linear/linear_utils.py b/qiskit/synthesis/linear/linear_utils.py index 662e475fb307..20325ab907cc 100644 --- a/qiskit/synthesis/linear/linear_utils.py +++ b/qiskit/synthesis/linear/linear_utils.py @@ -16,26 +16,26 @@ from qiskit.exceptions import QiskitError -def _calc_rank(A): - # This function takes as input a binary square matrix A - # Returns the rank of A over the binary field F_2 - n = A.shape[1] - X = np.identity(n, dtype=int) +def _calc_rank(mat): + # This function takes as input a binary square matrix mat + # Returns the rank of mat over the binary field F_2 + n = mat.shape[1] + xmat = np.identity(n, dtype=int) for i in range(n): - y = np.dot(A[i, :], X) % 2 + y = np.dot(mat[i, :], xmat) % 2 not_y = (y + 1) % 2 - good = X[:, np.nonzero(not_y)] + good = xmat[:, np.nonzero(not_y)] good = good[:, 0, :] - bad = X[:, np.nonzero(y)] + bad = xmat[:, np.nonzero(y)] bad = bad[:, 0, :] if bad.shape[1] > 0: bad = np.add(bad, np.roll(bad, 1, axis=1)) bad = bad % 2 bad = np.delete(bad, 0, axis=1) - X = np.concatenate((good, bad), axis=1) + xmat = np.concatenate((good, bad), axis=1) # now columns of X span the binary null-space of A - return n - X.shape[1] + return n - xmat.shape[1] def random_invertible_binary_matrix(num_qubits, seed=None): @@ -53,17 +53,17 @@ def random_invertible_binary_matrix(num_qubits, seed=None): return mat -def _gauss_elimination(A, ncols=None, full_elim=False): - """Gauss elimination of a matrix A with m rows and n columns. - If full_elim = True, it allows full elimination of A[:, 0 : ncols] - Returns the matrix A and the permutation perm that was done on the rows +def _gauss_elimination(mat, ncols=None, full_elim=False): + """Gauss elimination of a matrix mat with m rows and n columns. + If full_elim = True, it allows full elimination of mat[:, 0 : ncols] + Returns the matrix mat and the permutation perm that was done on the rows during the process.""" # Treat the matrix A as containing integer values - A = np.array(A, dtype=int, copy=False) + mat = np.array(mat, dtype=int, copy=False) - m = A.shape[0] # no. of rows - n = A.shape[1] # no. of columns + m = mat.shape[0] # no. of rows + n = mat.shape[1] # no. of columns if ncols is not None: n = min(n, ncols) # no. of active columns @@ -72,66 +72,66 @@ def _gauss_elimination(A, ncols=None, full_elim=False): r = 0 # current rank k = 0 # current pivot column while (r < m) and (k < n): - isNZ = False + is_non_zero = False new_r = r for j in range(k, n): for i in range(r, m): - if A[i][j]: - isNZ = True + if mat[i][j]: + is_non_zero = True k = j new_r = i break - if isNZ: + if is_non_zero: break - if not isNZ: - return A, perm # A is in the canonical form + if not is_non_zero: + return mat, perm # A is in the canonical form if new_r != r: - tmp = A[new_r].copy() - A[new_r] = A[r] - A[r] = tmp + tmp = mat[new_r].copy() + mat[new_r] = mat[r] + mat[r] = tmp perm[r], perm[new_r] = perm[new_r], perm[r] if full_elim: for i in range(0, r): - if A[i][k]: - A[i] = A[i] ^ A[r] + if mat[i][k]: + mat[i] = mat[i] ^ mat[r] for i in range(r + 1, m): - if A[i][k]: - A[i] = A[i] ^ A[r] + if mat[i][k]: + mat[i] = mat[i] ^ mat[r] r += 1 - return A, perm + return mat, perm -def calc_inverse_matrix(A, verify=False): - """Given a square numpy(dtype=int) matrix A, tries to compute its inverse. +def calc_inverse_matrix(mat, verify=False): + """Given a square numpy(dtype=int) matrix mat, tries to compute its inverse. Returns None if the matrix is not invertible, and the inverse otherwise.""" - if A.shape[0] != A.shape[1]: + if mat.shape[0] != mat.shape[1]: raise QiskitError("Matrix to invert is a non-square matrix.") - n = A.shape[0] + n = mat.shape[0] # concatenate the matrix and identity - B = np.concatenate((A, np.eye(n, dtype=int)), axis=1) - B, _ = _gauss_elimination(B, None, full_elim=True) + mat1 = np.concatenate((mat, np.eye(n, dtype=int)), axis=1) + mat1, _ = _gauss_elimination(mat1, None, full_elim=True) - r = _compute_rank_after_gauss_elim(B[:, 0:n]) + r = _compute_rank_after_gauss_elim(mat1[:, 0:n]) if r < n: raise QiskitError("The matrix is not invertible") - else: - C = B[:, n : 2 * n] + matinv = mat1[:, n : 2 * n] + + if verify: + mat2 = np.dot(mat, matinv) % 2 + assert np.array_equal(mat2, np.eye(n)) - if verify: - D = np.dot(A, C) % 2 - assert np.array_equal(D, np.eye(n)) - return C + return matinv -def _compute_rank_after_gauss_elim(A): +def _compute_rank_after_gauss_elim(mat): """Given a matrix A after Gaussian elimination, computes its rank (i.e. simply the number of nonzero rows)""" - return np.sum(A.any(axis=1)) + return np.sum(mat.any(axis=1)) From 48ac9c2005703a605042b7821a4c952578e04ad8 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 18 Aug 2022 13:08:19 +0300 Subject: [PATCH 08/39] add a function with 4 options to synthesize a CX circuit --- qiskit/synthesis/linear/linear_utils.py | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/qiskit/synthesis/linear/linear_utils.py b/qiskit/synthesis/linear/linear_utils.py index 20325ab907cc..35b124b2f3b0 100644 --- a/qiskit/synthesis/linear/linear_utils.py +++ b/qiskit/synthesis/linear/linear_utils.py @@ -12,6 +12,7 @@ """Utility functions for handling binary matrices.""" +import copy import numpy as np from qiskit.exceptions import QiskitError @@ -135,3 +136,50 @@ def _compute_rank_after_gauss_elim(mat): """Given a matrix A after Gaussian elimination, computes its rank (i.e. simply the number of nonzero rows)""" return np.sum(mat.any(axis=1)) + + +def transpose_cx_circ(qc): + """Transpose all cx gates in a circuit.""" + data = qc.data + for i in range(len(data)): + if data[i][0].name == "cx" and data[i].operation.num_qubits == 2: + data[i][1][0], data[i][1][1] = data[i][1][1], data[i][1][0] + + +def optimize_cx_4_options(function, mat, optimize_count=True): + # Get best implementation of CX, implementing M,M^(-1),M^T,M^(-1)^T + # if not choose_depth, get best cost + for i in range(4): + mat_cpy = copy.deepcopy(mat) + # i=1 inverse, i=2 transpose, i=3 transpose and inverse + if i == 0: + qc = function(mat) + if i == 1: + mat_cpy = calc_inverse_matrix(mat_cpy) + qc = function(mat_cpy) + qc = qc.inverse() + elif i == 2: + mat_cpy = np.transpose(mat_cpy) + qc = function(mat_cpy) + transpose_cx_circ(qc) + qc = qc.inverse() + elif i == 3: + mat_cpy = calc_inverse_matrix(np.transpose(mat_cpy)) + qc = function(mat_cpy) + transpose_cx_circ(qc) + + new_depth = qc.depth() + new_count = qc.count_ops()["cx"] + + if ( + i == 0 + or (optimize_count and best_count > new_count) + or (not optimize_count and best_depth > new_depth) + or (optimize_count and best_count == new_count and best_depth > new_depth) + or (not optimize_count and best_depth == new_depth and best_count > new_count) + ): + best_count = new_count + best_depth = new_depth + best_qc = qc + + return best_qc From c824d7488712d07716b6a77f0b2fe0b2c17689a9 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 21 Aug 2022 12:15:36 +0300 Subject: [PATCH 09/39] improve linear_utils code --- qiskit/synthesis/linear/linear_utils.py | 32 +++++++++++++------------ 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/qiskit/synthesis/linear/linear_utils.py b/qiskit/synthesis/linear/linear_utils.py index 35b124b2f3b0..aba04aa3b87e 100644 --- a/qiskit/synthesis/linear/linear_utils.py +++ b/qiskit/synthesis/linear/linear_utils.py @@ -18,6 +18,7 @@ def _calc_rank(mat): + """Calculate the rank of a square binary matrix.""" # This function takes as input a binary square matrix mat # Returns the rank of mat over the binary field F_2 n = mat.shape[1] @@ -41,7 +42,6 @@ def _calc_rank(mat): def random_invertible_binary_matrix(num_qubits, seed=None): """Generates a random invertible n x n binary matrix.""" - # This code is adapted from random_cnotdihedral if isinstance(seed, np.random.Generator): rng = seed else: @@ -141,19 +141,21 @@ def _compute_rank_after_gauss_elim(mat): def transpose_cx_circ(qc): """Transpose all cx gates in a circuit.""" data = qc.data - for i in range(len(data)): + for i, _ in enumerate(data): if data[i][0].name == "cx" and data[i].operation.num_qubits == 2: data[i][1][0], data[i][1][1] = data[i][1][1], data[i][1][0] def optimize_cx_4_options(function, mat, optimize_count=True): - # Get best implementation of CX, implementing M,M^(-1),M^T,M^(-1)^T - # if not choose_depth, get best cost - for i in range(4): + """Get best implementation of CX, implementing M,M^(-1),M^T,M^(-1)^T""" + qc = function(mat) + best_qc = qc + best_depth = qc.depth() + best_count = qc.count_ops()["cx"] + + for i in range(1, 4): mat_cpy = copy.deepcopy(mat) # i=1 inverse, i=2 transpose, i=3 transpose and inverse - if i == 0: - qc = function(mat) if i == 1: mat_cpy = calc_inverse_matrix(mat_cpy) qc = function(mat_cpy) @@ -170,14 +172,14 @@ def optimize_cx_4_options(function, mat, optimize_count=True): new_depth = qc.depth() new_count = qc.count_ops()["cx"] - - if ( - i == 0 - or (optimize_count and best_count > new_count) - or (not optimize_count and best_depth > new_depth) - or (optimize_count and best_count == new_count and best_depth > new_depth) - or (not optimize_count and best_depth == new_depth and best_count > new_count) - ): + better_count = (optimize_count and best_count > new_count) or ( + not optimize_count and best_depth == new_depth and best_count > new_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 best_qc = qc From ec0043f9126fc8318f796422a9a30801bcbeae60 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 14 Sep 2022 11:27:29 +0300 Subject: [PATCH 10/39] refactor transpiler/test_synthesis to synthesis/test_gray_synthesis --- .../linear/{linear_utils.py => linear_matrix_utils.py} | 0 test/python/circuit/library/test_linear_function.py | 2 +- .../test_synthesis.py => synthesis/test_gray_synthesis.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename qiskit/synthesis/linear/{linear_utils.py => linear_matrix_utils.py} (100%) rename test/python/{transpiler/test_synthesis.py => synthesis/test_gray_synthesis.py} (100%) diff --git a/qiskit/synthesis/linear/linear_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py similarity index 100% rename from qiskit/synthesis/linear/linear_utils.py rename to qiskit/synthesis/linear/linear_matrix_utils.py diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index 61f77f7425c6..b7ca592ace80 100644 --- a/test/python/circuit/library/test_linear_function.py +++ b/test/python/circuit/library/test_linear_function.py @@ -116,7 +116,7 @@ def test_conversion_to_linear_function_and_back(self, num_qubits): def test_patel_markov_hayes(self): """Checks the explicit example from Patel-Markov-Hayes's paper.""" - # This code is adapted from test_synthesis.py + # This code is adapted from test_gray_synthesis.py binary_matrix = [ [1, 1, 0, 0, 0, 0], [1, 0, 0, 1, 1, 0], diff --git a/test/python/transpiler/test_synthesis.py b/test/python/synthesis/test_gray_synthesis.py similarity index 100% rename from test/python/transpiler/test_synthesis.py rename to test/python/synthesis/test_gray_synthesis.py From 4f97b6c6598618dfd71b7379090cb8499b568631 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 14 Sep 2022 11:29:18 +0300 Subject: [PATCH 11/39] split linear_utils into two files --- qiskit/synthesis/linear/__init__.py | 2 +- .../synthesis/linear/linear_circuits_utils.py | 64 +++++++++++++++++++ .../synthesis/linear/linear_matrix_utils.py | 50 --------------- 3 files changed, 65 insertions(+), 51 deletions(-) create mode 100644 qiskit/synthesis/linear/linear_circuits_utils.py diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index 17e11804bdb7..d46209c310aa 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -14,4 +14,4 @@ from .graysynth import graysynth, cnot_synth -from .linear_utils import random_invertible_binary_matrix, calc_inverse_matrix +from .linear_matrix_utils import random_invertible_binary_matrix, calc_inverse_matrix diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py new file mode 100644 index 000000000000..4ea73b97d727 --- /dev/null +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -0,0 +1,64 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Utility functions for handling linear reversible circuits.""" + +import copy +from . import calc_inverse_matrix + +def transpose_cx_circ(qc): + """Transpose all cx gates in a circuit.""" + data = qc.data + for i, _ in enumerate(data): + if data[i][0].name == "cx" and data[i].operation.num_qubits == 2: + data[i][1][0], data[i][1][1] = data[i][1][1], data[i][1][0] + + +def optimize_cx_4_options(function, mat, optimize_count=True): + """Get best implementation of CX, implementing M,M^(-1),M^T,M^(-1)^T""" + qc = function(mat) + best_qc = qc + best_depth = qc.depth() + best_count = qc.count_ops()["cx"] + + for i in range(1, 4): + mat_cpy = copy.deepcopy(mat) + # i=1 inverse, i=2 transpose, i=3 transpose and inverse + if i == 1: + mat_cpy = calc_inverse_matrix(mat_cpy) + qc = function(mat_cpy) + qc = qc.inverse() + elif i == 2: + mat_cpy = np.transpose(mat_cpy) + qc = function(mat_cpy) + transpose_cx_circ(qc) + qc = qc.inverse() + elif i == 3: + mat_cpy = calc_inverse_matrix(np.transpose(mat_cpy)) + qc = function(mat_cpy) + transpose_cx_circ(qc) + + new_depth = qc.depth() + new_count = qc.count_ops()["cx"] + better_count = (optimize_count and best_count > new_count) or ( + not optimize_count and best_depth == new_depth and best_count > new_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 + best_qc = qc + + return best_qc diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index aba04aa3b87e..30eabbf7f634 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -12,7 +12,6 @@ """Utility functions for handling binary matrices.""" -import copy import numpy as np from qiskit.exceptions import QiskitError @@ -136,52 +135,3 @@ def _compute_rank_after_gauss_elim(mat): """Given a matrix A after Gaussian elimination, computes its rank (i.e. simply the number of nonzero rows)""" return np.sum(mat.any(axis=1)) - - -def transpose_cx_circ(qc): - """Transpose all cx gates in a circuit.""" - data = qc.data - for i, _ in enumerate(data): - if data[i][0].name == "cx" and data[i].operation.num_qubits == 2: - data[i][1][0], data[i][1][1] = data[i][1][1], data[i][1][0] - - -def optimize_cx_4_options(function, mat, optimize_count=True): - """Get best implementation of CX, implementing M,M^(-1),M^T,M^(-1)^T""" - qc = function(mat) - best_qc = qc - best_depth = qc.depth() - best_count = qc.count_ops()["cx"] - - for i in range(1, 4): - mat_cpy = copy.deepcopy(mat) - # i=1 inverse, i=2 transpose, i=3 transpose and inverse - if i == 1: - mat_cpy = calc_inverse_matrix(mat_cpy) - qc = function(mat_cpy) - qc = qc.inverse() - elif i == 2: - mat_cpy = np.transpose(mat_cpy) - qc = function(mat_cpy) - transpose_cx_circ(qc) - qc = qc.inverse() - elif i == 3: - mat_cpy = calc_inverse_matrix(np.transpose(mat_cpy)) - qc = function(mat_cpy) - transpose_cx_circ(qc) - - new_depth = qc.depth() - new_count = qc.count_ops()["cx"] - better_count = (optimize_count and best_count > new_count) or ( - not optimize_count and best_depth == new_depth and best_count > new_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 - best_qc = qc - - return best_qc From e56f26cc32c3b5a86af5f8415fcf2e202a17c2ea Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 14 Sep 2022 11:30:55 +0300 Subject: [PATCH 12/39] style --- qiskit/synthesis/linear/linear_circuits_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index 4ea73b97d727..8410868f11c6 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -15,6 +15,7 @@ import copy from . import calc_inverse_matrix + def transpose_cx_circ(qc): """Transpose all cx gates in a circuit.""" data = qc.data From 01ffbed5dee34aee6a008ed1d5de6c342b2f1861 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 14 Sep 2022 11:34:31 +0300 Subject: [PATCH 13/39] add test/python/synthesis init file --- test/python/synthesis/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test/python/synthesis/__init__.py diff --git a/test/python/synthesis/__init__.py b/test/python/synthesis/__init__.py new file mode 100644 index 000000000000..f7c02dacd381 --- /dev/null +++ b/test/python/synthesis/__init__.py @@ -0,0 +1,13 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Qiskit synthesis unit tests.""" From aec2006f6436f8c236a8fc589720eacd1e3c022d Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 14 Sep 2022 12:22:29 +0300 Subject: [PATCH 14/39] unify linear utils code --- .../synthesis/linear/linear_circuits_utils.py | 1 + .../synthesis/linear/linear_matrix_utils.py | 36 +++---------------- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index 8410868f11c6..edc4eb197df2 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -13,6 +13,7 @@ """Utility functions for handling linear reversible circuits.""" import copy +import numpy as np from . import calc_inverse_matrix diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index 30eabbf7f634..1b132be28a96 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -16,29 +16,6 @@ from qiskit.exceptions import QiskitError -def _calc_rank(mat): - """Calculate the rank of a square binary matrix.""" - # This function takes as input a binary square matrix mat - # Returns the rank of mat over the binary field F_2 - n = mat.shape[1] - xmat = np.identity(n, dtype=int) - - for i in range(n): - y = np.dot(mat[i, :], xmat) % 2 - not_y = (y + 1) % 2 - good = xmat[:, np.nonzero(not_y)] - good = good[:, 0, :] - bad = xmat[:, np.nonzero(y)] - bad = bad[:, 0, :] - if bad.shape[1] > 0: - bad = np.add(bad, np.roll(bad, 1, axis=1)) - bad = bad % 2 - bad = np.delete(bad, 0, axis=1) - xmat = np.concatenate((good, bad), axis=1) - # now columns of X span the binary null-space of A - return n - xmat.shape[1] - - def random_invertible_binary_matrix(num_qubits, seed=None): """Generates a random invertible n x n binary matrix.""" if isinstance(seed, np.random.Generator): @@ -49,15 +26,15 @@ def random_invertible_binary_matrix(num_qubits, seed=None): rank = 0 while rank != num_qubits: mat = rng.integers(2, size=(num_qubits, num_qubits)) - rank = _calc_rank(mat) + mat = _gauss_elimination(mat) + rank = _compute_rank_after_gauss_elim(mat) return mat def _gauss_elimination(mat, ncols=None, full_elim=False): """Gauss elimination of a matrix mat with m rows and n columns. If full_elim = True, it allows full elimination of mat[:, 0 : ncols] - Returns the matrix mat and the permutation perm that was done on the rows - during the process.""" + Returns the matrix mat.""" # Treat the matrix A as containing integer values mat = np.array(mat, dtype=int, copy=False) @@ -67,8 +44,6 @@ def _gauss_elimination(mat, ncols=None, full_elim=False): if ncols is not None: n = min(n, ncols) # no. of active columns - perm = np.array(range(n)) # permutation on the rows - r = 0 # current rank k = 0 # current pivot column while (r < m) and (k < n): @@ -84,13 +59,12 @@ def _gauss_elimination(mat, ncols=None, full_elim=False): if is_non_zero: break if not is_non_zero: - return mat, perm # A is in the canonical form + return mat # A is in the canonical form if new_r != r: tmp = mat[new_r].copy() mat[new_r] = mat[r] mat[r] = tmp - perm[r], perm[new_r] = perm[new_r], perm[r] if full_elim: for i in range(0, r): @@ -102,7 +76,7 @@ def _gauss_elimination(mat, ncols=None, full_elim=False): mat[i] = mat[i] ^ mat[r] r += 1 - return mat, perm + return mat def calc_inverse_matrix(mat, verify=False): From e8b1121cd1a4452c675d099a8176f88977acfadd Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 15 Sep 2022 12:14:46 +0300 Subject: [PATCH 15/39] add check_invertible_binary_matrix function --- .../library/generalized_gates/linear_function.py | 4 ++-- qiskit/synthesis/linear/__init__.py | 6 +++++- qiskit/synthesis/linear/linear_matrix_utils.py | 14 +++++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index f86471c12893..9e3d71c0d869 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -16,6 +16,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit, Gate from qiskit.circuit.exceptions import CircuitError +from qiskit.synthesis.linear import check_invertible_binary_matrix class LinearFunction(Gate): @@ -110,8 +111,7 @@ def __init__( # Optionally, check that the matrix is invertible if validate_input: - det = np.linalg.det(linear) % 2 - if not np.allclose(det, 1): + if not check_invertible_binary_matrix(linear): raise CircuitError( "A linear function must be represented by an invertible matrix." ) diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index d46209c310aa..a3f4601ef66c 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -14,4 +14,8 @@ from .graysynth import graysynth, cnot_synth -from .linear_matrix_utils import random_invertible_binary_matrix, calc_inverse_matrix +from .linear_matrix_utils import ( + random_invertible_binary_matrix, + calc_inverse_matrix, + check_invertible_binary_matrix, +) diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index 1b132be28a96..8396b22cc1db 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -16,6 +16,16 @@ from qiskit.exceptions import QiskitError +def check_invertible_binary_matrix(mat): + """Check that a binary matrix is invertible.""" + if len(mat.shape) != 2 or mat.shape[0] != mat.shape[1]: + return False + + mat = _gauss_elimination(mat) + rank = _compute_rank_after_gauss_elim(mat) + return rank == mat.shape[0] + + def random_invertible_binary_matrix(num_qubits, seed=None): """Generates a random invertible n x n binary matrix.""" if isinstance(seed, np.random.Generator): @@ -62,9 +72,7 @@ def _gauss_elimination(mat, ncols=None, full_elim=False): return mat # A is in the canonical form if new_r != r: - tmp = mat[new_r].copy() - mat[new_r] = mat[r] - mat[r] = tmp + mat[[r, new_r]] = mat[[new_r, r]] if full_elim: for i in range(0, r): From 50b0998f6d8a7ecd95240d349f0314da5eba1519 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 19 Oct 2022 13:07:39 +0300 Subject: [PATCH 16/39] update import in high_level_synthesis transpiler pass --- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 0edb47fab932..20ceb1fc64cf 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -19,7 +19,7 @@ from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.transpiler.exceptions import TranspilerError from qiskit.quantum_info import decompose_clifford -from qiskit.transpiler.synthesis import cnot_synth +from qiskit.synthesis.linear import cnot_synth from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin From e684490d4bbed45f33b1c5143b3cf27177468813 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 19 Oct 2022 13:08:59 +0300 Subject: [PATCH 17/39] update transpose_cx_circ, add docstrings --- .../synthesis/linear/linear_circuits_utils.py | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index edc4eb197df2..5b68d3383f00 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -14,19 +14,51 @@ import copy import numpy as np -from . import calc_inverse_matrix +from qiskit import QuantumCircuit +from qiskit.exceptions import QiskitError +from qiskit.circuit.exceptions import CircuitError +from . import calc_inverse_matrix, check_invertible_binary_matrix def transpose_cx_circ(qc): - """Transpose all cx gates in a circuit.""" - data = qc.data - for i, _ in enumerate(data): - if data[i][0].name == "cx" and data[i].operation.num_qubits == 2: - data[i][1][0], data[i][1][1] = data[i][1][1], data[i][1][0] + """Takes a circuit having only CX gates, and calculates its transpose. + This is done by recursively replacing CX(i, j) with CX(j, i) in all instructions. + + Args: + qc: a QuantumCircuit containing only CX gates. + + Returns: + QuantumCircuit: the transposed circuit. + + Raises: + CircuitErrors: if qc has a non-CX gate. + """ + tranposed_circ = QuantumCircuit(qc.qubits, qc.clbits, name=qc.name + "_transpose") + for instruction in qc.data: + if instruction.operation.name != "cx": + raise CircuitError("The circuit contains non-CX gates.") + if instruction.operation.num_qubits != 2: + raise CircuitError("The circuit contains a CX gates which is not on two qubits.") + tranposed_circ._append(instruction.replace(qubits=list(instruction.qubits).reverse())) + return tranposed_circ def optimize_cx_4_options(function, mat, optimize_count=True): - """Get best implementation of CX, implementing M,M^(-1),M^T,M^(-1)^T""" + """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. + + Args: + function: the synthesis function. + mat: a binary invertible matrix. + optimize_count: True if the number of CX gates in optimize, False if the depth is optimized. + + Returns: + QuantumCircuit: an optimized QuantumCircuit, having the best depth or CX count of the four options. + """ + if not check_invertible_binary_matrix(mat): + raise QiskitError("The matrix is not invertible.") + qc = function(mat) best_qc = qc best_depth = qc.depth() From 32dc45370d3660cb9fba8fd6ef368ed504a9cfe5 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 19 Oct 2022 13:09:35 +0300 Subject: [PATCH 18/39] minor fix in calc_inverse_matrix --- qiskit/synthesis/linear/linear_matrix_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index 8396b22cc1db..64dbeed0c367 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -97,7 +97,7 @@ def calc_inverse_matrix(mat, verify=False): n = mat.shape[0] # concatenate the matrix and identity mat1 = np.concatenate((mat, np.eye(n, dtype=int)), axis=1) - mat1, _ = _gauss_elimination(mat1, None, full_elim=True) + mat1 = _gauss_elimination(mat1, None, full_elim=True) r = _compute_rank_after_gauss_elim(mat1[:, 0:n]) From 760bb2b25993dc4918a725d969444072eded2241 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 19 Oct 2022 14:22:21 +0300 Subject: [PATCH 19/39] add types in docstrings --- qiskit/synthesis/linear/linear_circuits_utils.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index 5b68d3383f00..bfee23b71da5 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -14,13 +14,14 @@ import copy import numpy as np +from typing import Callable from qiskit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.circuit.exceptions import CircuitError from . import calc_inverse_matrix, check_invertible_binary_matrix -def transpose_cx_circ(qc): +def transpose_cx_circ(qc: QuantumCircuit): """Takes a circuit having only CX gates, and calculates its transpose. This is done by recursively replacing CX(i, j) with CX(j, i) in all instructions. @@ -31,7 +32,8 @@ def transpose_cx_circ(qc): QuantumCircuit: the transposed circuit. Raises: - CircuitErrors: if qc has a non-CX gate. + CircuitError: if qc has a non-CX gate. + CircuitError: if a CX gate is not on two qubits. """ tranposed_circ = QuantumCircuit(qc.qubits, qc.clbits, name=qc.name + "_transpose") for instruction in qc.data: @@ -43,7 +45,7 @@ def transpose_cx_circ(qc): return tranposed_circ -def optimize_cx_4_options(function, mat, optimize_count=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. @@ -54,7 +56,10 @@ def optimize_cx_4_options(function, mat, optimize_count=True): optimize_count: True if the number of CX gates in optimize, False if the depth is optimized. Returns: - QuantumCircuit: an optimized QuantumCircuit, having the best depth or CX count of the four options. + QuantumCircuit: an optimized QuantumCircuit, has the best depth or CX count of the four options. + + Raises: + QiskitError: if mat is not an invertible matrix. """ if not check_invertible_binary_matrix(mat): raise QiskitError("The matrix is not invertible.") From 6fae16269a436b056265cc47201cb605cbe9e4ec Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 19 Oct 2022 15:50:26 +0300 Subject: [PATCH 20/39] add tests for linear synthesis functions --- .../python/synthesis/test_linear_synthesis.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 test/python/synthesis/test_linear_synthesis.py diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py new file mode 100644 index 000000000000..c2a5330d73b9 --- /dev/null +++ b/test/python/synthesis/test_linear_synthesis.py @@ -0,0 +1,66 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test linear reversible circuits synthesis functions.""" + +import unittest + +from qiskit.circuit.library import LinearFunction +from qiskit.synthesis.linear import cnot_synth, random_invertible_binary_matrix +from qiskit.synthesis.linear.linear_circuits_utils import * +from qiskit.test import QiskitTestCase + + +class TestLinearSynth(QiskitTestCase): + """Test the linear reversible circuit synthesis functions.""" + + def test_lnn_circuit(self): + """Test the synthesis of a CX circuit with LNN connectivity.""" + + n = 5 + qc = QuantumCircuit(n) + for i in range(n - 1): + qc.cx(i, i + 1) + mat = LinearFunction(qc).linear + + for optimized in [True, False]: + optimized_qc = optimize_cx_4_options(cnot_synth, mat, optimize_count=optimized) + self.assertEqual(optimized_qc.depth(), 4) + self.assertEqual(optimized_qc.count_ops()["cx"], 4) + + def test_full_circuit(self): + """Test the synthesis of a CX circuit with full connectivity.""" + + n = 5 + qc = QuantumCircuit(n) + for i in range(n): + for j in range(i + 1, n): + qc.cx(i, j) + mat = LinearFunction(qc).linear + + for optimized in [True, False]: + optimized_qc = optimize_cx_4_options(cnot_synth, mat, optimize_count=optimized) + self.assertEqual(optimized_qc.depth(), 4) + self.assertEqual(optimized_qc.count_ops()["cx"], 4) + + def test_transpose_circ(self): + """Test the transpose_cx_circ() function.""" + n = 5 + mat = random_invertible_binary_matrix(5, seed=1234) + qc = cnot_synth(mat) + transposed_qc = transpose_cx_circ(qc) + transposed_mat = LinearFunction(transposed_qc).linear.astype(int) + assert (mat.transpose() == transposed_mat).all() + + +if __name__ == "__main__": + unittest.main() From 03456107296ca7aa67eda67048f728eae6fc61f8 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 19 Oct 2022 15:51:27 +0300 Subject: [PATCH 21/39] fix transpose_cx_circ function --- qiskit/synthesis/linear/linear_circuits_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index bfee23b71da5..5808c139ac12 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -13,8 +13,8 @@ """Utility functions for handling linear reversible circuits.""" import copy -import numpy as np from typing import Callable +import numpy as np from qiskit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.circuit.exceptions import CircuitError @@ -36,12 +36,12 @@ def transpose_cx_circ(qc: QuantumCircuit): CircuitError: if a CX gate is not on two qubits. """ tranposed_circ = QuantumCircuit(qc.qubits, qc.clbits, name=qc.name + "_transpose") - for instruction in qc.data: + for instruction in reversed(qc.data): if instruction.operation.name != "cx": raise CircuitError("The circuit contains non-CX gates.") if instruction.operation.num_qubits != 2: raise CircuitError("The circuit contains a CX gates which is not on two qubits.") - tranposed_circ._append(instruction.replace(qubits=list(instruction.qubits).reverse())) + tranposed_circ._append(instruction.replace(qubits=reversed(instruction.qubits))) return tranposed_circ @@ -80,11 +80,11 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b mat_cpy = np.transpose(mat_cpy) qc = function(mat_cpy) transpose_cx_circ(qc) - qc = qc.inverse() elif i == 3: mat_cpy = calc_inverse_matrix(np.transpose(mat_cpy)) qc = function(mat_cpy) transpose_cx_circ(qc) + qc = qc.inverse() new_depth = qc.depth() new_count = qc.count_ops()["cx"] From 671904505a347d4dacbab69722fd19d44b5c328d Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 19 Oct 2022 16:59:50 +0300 Subject: [PATCH 22/39] add docstrings in linear_matrix_utils --- .../synthesis/linear/linear_matrix_utils.py | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index 64dbeed0c367..2e233b0a2513 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -16,8 +16,15 @@ from qiskit.exceptions import QiskitError -def check_invertible_binary_matrix(mat): - """Check that a binary matrix is invertible.""" +def check_invertible_binary_matrix(mat: np.ndarray): + """Check that a binary matrix is invertible. + + Args: + mat: a binary matrix. + + Returns: + True if mat in invertible and False otherwise. + """ if len(mat.shape) != 2 or mat.shape[0] != mat.shape[1]: return False @@ -26,8 +33,16 @@ def check_invertible_binary_matrix(mat): return rank == mat.shape[0] -def random_invertible_binary_matrix(num_qubits, seed=None): - """Generates a random invertible n x n binary matrix.""" +def random_invertible_binary_matrix(num_qubits: int, seed: np.random.Generator = None): + """Generates a random invertible n x n binary matrix. + + Args: + num_qubits: the matrix size. + seed: a random seed. + + Returns: + A random invertible binary matrix of size num_qubits. + """ if isinstance(seed, np.random.Generator): rng = seed else: @@ -87,9 +102,20 @@ def _gauss_elimination(mat, ncols=None, full_elim=False): return mat -def calc_inverse_matrix(mat, verify=False): +def calc_inverse_matrix(mat: np.ndarray, verify: bool = False): """Given a square numpy(dtype=int) matrix mat, tries to compute its inverse. - Returns None if the matrix is not invertible, and the inverse otherwise.""" + Returns None if the matrix is not invertible, and the inverse otherwise. + + Args: + mat: a boolean square matrix. + verify: if True asserts that the multiplication of mat and its inverse is the identity matrix. + + Returns: + None if the matrix is not invertible, and the inverse matrix otherwise. + + Raises: + QiskitError: if the matrix is not square. + """ if mat.shape[0] != mat.shape[1]: raise QiskitError("Matrix to invert is a non-square matrix.") From 302d4d080d1f9e230520fec8798339db111e4b6f Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 19 Oct 2022 17:00:41 +0300 Subject: [PATCH 23/39] update import in test --- test/python/synthesis/test_linear_synthesis.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index c2a5330d73b9..e07b66d27cda 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -14,9 +14,10 @@ import unittest +from qiskit import QuantumCircuit from qiskit.circuit.library import LinearFunction from qiskit.synthesis.linear import cnot_synth, random_invertible_binary_matrix -from qiskit.synthesis.linear.linear_circuits_utils import * +from qiskit.synthesis.linear.linear_circuits_utils import transpose_cx_circ, optimize_cx_4_options from qiskit.test import QiskitTestCase @@ -55,7 +56,7 @@ def test_full_circuit(self): def test_transpose_circ(self): """Test the transpose_cx_circ() function.""" n = 5 - mat = random_invertible_binary_matrix(5, seed=1234) + mat = random_invertible_binary_matrix(n, seed=1234) qc = cnot_synth(mat) transposed_qc = transpose_cx_circ(qc) transposed_mat = LinearFunction(transposed_qc).linear.astype(int) From 9302658773dfa4f6f159718dc0f6fd5f9af79e5a Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 20 Oct 2022 10:54:10 +0300 Subject: [PATCH 24/39] fix docs in linear matrix utils --- qiskit/synthesis/linear/linear_matrix_utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index 2e233b0a2513..a9adb8806f7d 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -23,7 +23,7 @@ def check_invertible_binary_matrix(mat: np.ndarray): mat: a binary matrix. Returns: - True if mat in invertible and False otherwise. + bool: True if mat in invertible and False otherwise. """ if len(mat.shape) != 2 or mat.shape[0] != mat.shape[1]: return False @@ -41,7 +41,7 @@ def random_invertible_binary_matrix(num_qubits: int, seed: np.random.Generator = seed: a random seed. Returns: - A random invertible binary matrix of size num_qubits. + np.ndarray: A random invertible binary matrix of size num_qubits. """ if isinstance(seed, np.random.Generator): rng = seed @@ -104,17 +104,17 @@ def _gauss_elimination(mat, ncols=None, full_elim=False): def calc_inverse_matrix(mat: np.ndarray, verify: bool = False): """Given a square numpy(dtype=int) matrix mat, tries to compute its inverse. - Returns None if the matrix is not invertible, and the inverse otherwise. Args: mat: a boolean square matrix. verify: if True asserts that the multiplication of mat and its inverse is the identity matrix. Returns: - None if the matrix is not invertible, and the inverse matrix otherwise. + np.ndarray: the inverse matrix. Raises: QiskitError: if the matrix is not square. + QiskitError: if the matrix is not invertible. """ if mat.shape[0] != mat.shape[1]: @@ -128,7 +128,7 @@ def calc_inverse_matrix(mat: np.ndarray, verify: bool = False): r = _compute_rank_after_gauss_elim(mat1[:, 0:n]) if r < n: - raise QiskitError("The matrix is not invertible") + raise QiskitError("The matrix is not invertible.") matinv = mat1[:, n : 2 * n] From c83eaec01df4cb0787770c5fe032aa441b88b376 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 20 Oct 2022 10:54:57 +0300 Subject: [PATCH 25/39] add a test for invertible matrices --- test/python/synthesis/test_linear_synthesis.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index e07b66d27cda..f4249583beba 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -14,9 +14,11 @@ import unittest +import numpy as np from qiskit import QuantumCircuit from qiskit.circuit.library import LinearFunction -from qiskit.synthesis.linear import cnot_synth, random_invertible_binary_matrix +from qiskit.synthesis.linear import cnot_synth, random_invertible_binary_matrix, \ + check_invertible_binary_matrix, calc_inverse_matrix from qiskit.synthesis.linear.linear_circuits_utils import transpose_cx_circ, optimize_cx_4_options from qiskit.test import QiskitTestCase @@ -60,7 +62,18 @@ def test_transpose_circ(self): qc = cnot_synth(mat) transposed_qc = transpose_cx_circ(qc) transposed_mat = LinearFunction(transposed_qc).linear.astype(int) - assert (mat.transpose() == transposed_mat).all() + self.assertTrue ((mat.transpose() == transposed_mat).all()) + + + def test_invertible_matrix(self): + """Test the functions for generating a random invertible matrix and inverting it.""" + n = 5 + mat = random_invertible_binary_matrix(n, seed=1234) + out = check_invertible_binary_matrix(mat) + mat_inv = calc_inverse_matrix(mat, verify = True) + mat_out = np.dot(mat, mat_inv) % 2 + self.assertTrue (np.array_equal(mat_out, np.eye(n))) + self.assertTrue (out) if __name__ == "__main__": From 9dfb7843eaa1445e57ef6b62b45c231600d3b2b7 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 20 Oct 2022 10:56:10 +0300 Subject: [PATCH 26/39] style changes --- test/python/synthesis/test_linear_synthesis.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index f4249583beba..a2f3d5bc89f7 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -17,8 +17,12 @@ import numpy as np from qiskit import QuantumCircuit from qiskit.circuit.library import LinearFunction -from qiskit.synthesis.linear import cnot_synth, random_invertible_binary_matrix, \ - check_invertible_binary_matrix, calc_inverse_matrix +from qiskit.synthesis.linear import ( + cnot_synth, + random_invertible_binary_matrix, + check_invertible_binary_matrix, + calc_inverse_matrix, +) from qiskit.synthesis.linear.linear_circuits_utils import transpose_cx_circ, optimize_cx_4_options from qiskit.test import QiskitTestCase @@ -62,18 +66,17 @@ def test_transpose_circ(self): qc = cnot_synth(mat) transposed_qc = transpose_cx_circ(qc) transposed_mat = LinearFunction(transposed_qc).linear.astype(int) - self.assertTrue ((mat.transpose() == transposed_mat).all()) - + self.assertTrue((mat.transpose() == transposed_mat).all()) def test_invertible_matrix(self): """Test the functions for generating a random invertible matrix and inverting it.""" n = 5 mat = random_invertible_binary_matrix(n, seed=1234) out = check_invertible_binary_matrix(mat) - mat_inv = calc_inverse_matrix(mat, verify = True) + mat_inv = calc_inverse_matrix(mat, verify=True) mat_out = np.dot(mat, mat_inv) % 2 - self.assertTrue (np.array_equal(mat_out, np.eye(n))) - self.assertTrue (out) + self.assertTrue(np.array_equal(mat_out, np.eye(n))) + self.assertTrue(out) if __name__ == "__main__": From 40336c1d87dd7b8c1f86fe0ea438f15b4cbfc29f Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 20 Oct 2022 11:32:20 +0300 Subject: [PATCH 27/39] add release notes --- ...near_function_synthesis_utils-f2f96924ca45e1fb.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml diff --git a/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml new file mode 100644 index 000000000000..6060e5808257 --- /dev/null +++ b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Add new internal utility functions to handle linear functions and circuits: + A function that generates a random invertible binary matrix, + a function that checks that a binary matrix is invertible, and a function that calculates its inverse. + A function that calculates the transpose of a linear reversible circuit, + and a function that takes a binary invertible matrix A, + tries to synthesize A, A^{-1}, A^t and A^{-t} and chooses the best CX cost and/or depth. + The internal file graysynth.py is moved from qiskit.transpiler to qiskit.synthesis.linear. From 4a0a5550996c2728440f97fcb6deb3c0b55ed62f Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 23 Oct 2022 10:47:02 +0300 Subject: [PATCH 28/39] fix random_invertible_binary_matrix --- qiskit/synthesis/linear/linear_matrix_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index a9adb8806f7d..9201ee525cb2 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -51,8 +51,9 @@ def random_invertible_binary_matrix(num_qubits: int, seed: np.random.Generator = rank = 0 while rank != num_qubits: mat = rng.integers(2, size=(num_qubits, num_qubits)) - mat = _gauss_elimination(mat) - rank = _compute_rank_after_gauss_elim(mat) + mat_gauss = mat.copy() + mat_gauss = _gauss_elimination(mat_gauss) + rank = _compute_rank_after_gauss_elim(mat_gauss) return mat From e3d84e57da698f0ccce08860eef39ec384bd9e34 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Mon, 24 Oct 2022 17:06:11 +0300 Subject: [PATCH 29/39] update following review comments --- .../synthesis/linear/linear_circuits_utils.py | 13 ++++------ .../synthesis/linear/linear_matrix_utils.py | 5 +++- ...tion_synthesis_utils-f2f96924ca45e1fb.yaml | 25 ++++++++++++++----- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index 5808c139ac12..b9dfa2d2260b 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -33,16 +33,13 @@ def transpose_cx_circ(qc: QuantumCircuit): Raises: CircuitError: if qc has a non-CX gate. - CircuitError: if a CX gate is not on two qubits. """ - tranposed_circ = QuantumCircuit(qc.qubits, qc.clbits, name=qc.name + "_transpose") + transposed_circ = QuantumCircuit(qc.qubits, qc.clbits, name=qc.name + "_transpose") for instruction in reversed(qc.data): if instruction.operation.name != "cx": raise CircuitError("The circuit contains non-CX gates.") - if instruction.operation.num_qubits != 2: - raise CircuitError("The circuit contains a CX gates which is not on two qubits.") - tranposed_circ._append(instruction.replace(qubits=reversed(instruction.qubits))) - return tranposed_circ + transposed_circ._append(instruction.replace(qubits=reversed(instruction.qubits))) + return transposed_circ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: bool = True): @@ -79,11 +76,11 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b elif i == 2: mat_cpy = np.transpose(mat_cpy) qc = function(mat_cpy) - transpose_cx_circ(qc) + qc = transpose_cx_circ(qc) elif i == 3: mat_cpy = calc_inverse_matrix(np.transpose(mat_cpy)) qc = function(mat_cpy) - transpose_cx_circ(qc) + qc = transpose_cx_circ(qc) qc = qc.inverse() new_depth = qc.depth() diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index 9201ee525cb2..cc1ac7642249 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -13,6 +13,7 @@ """Utility functions for handling binary matrices.""" import numpy as np +from typing import Optional from qiskit.exceptions import QiskitError @@ -33,7 +34,9 @@ def check_invertible_binary_matrix(mat: np.ndarray): return rank == mat.shape[0] -def random_invertible_binary_matrix(num_qubits: int, seed: np.random.Generator = None): +def random_invertible_binary_matrix( + num_qubits: int, seed: Optional[np.random.Generator, int] = None +): """Generates a random invertible n x n binary matrix. Args: diff --git a/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml index 6060e5808257..e028a0eaa4d6 100644 --- a/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml +++ b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml @@ -2,9 +2,22 @@ features: - | Add new internal utility functions to handle linear functions and circuits: - A function that generates a random invertible binary matrix, - a function that checks that a binary matrix is invertible, and a function that calculates its inverse. - A function that calculates the transpose of a linear reversible circuit, - and a function that takes a binary invertible matrix A, - tries to synthesize A, A^{-1}, A^t and A^{-t} and chooses the best CX cost and/or depth. - The internal file graysynth.py is moved from qiskit.transpiler to qiskit.synthesis.linear. + A utility function that checks whether a NxN binary matrix is invertible and a utility function + that computes an inverse of a NxN invertible binary matrix. + A utility function that calculates the transpose of a linear reversible circuit. + Implement the following "meta" idea for synthesizing linear functions: + given an invertible binary matrix A and a synthesis algorithm f: A -> QuantumCircuit, + in addition to applying f to A, we can also apply f to A^{-1} and invert the computed circuit, + apply f to A^{t} and transpose the computed circuit, and to apply f to A^t^{-1} and to invert + and transpose the computed circuit, thus leading to 4 generally different synthesized circuits for A, + allowing to pick the best one in terms of depth or the number of gates. +fixes: + - | + Provide an internal utility function to generate a random NxN invertible binary matrix. + This function is completely robust, as opposed to the previously implemented method based on + computing determinant (via np.linalg.det) and checking that it's close to 1, + where we already saw cases of floating point errors leading to an incorrect result (for N>100). +upgrades: + - | + Improve code structure by moving the internal code graysynth.py from qiskit/transpiler/synthesis + to qiskit/synthesis/linear. From 131019956de2c44d3f8cab004415017b2deacf01 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Mon, 24 Oct 2022 17:27:23 +0300 Subject: [PATCH 30/39] fix type hint --- qiskit/synthesis/linear/linear_matrix_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index cc1ac7642249..034f2874de2f 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -13,7 +13,7 @@ """Utility functions for handling binary matrices.""" import numpy as np -from typing import Optional +from typing import Optional, Union from qiskit.exceptions import QiskitError @@ -35,7 +35,7 @@ def check_invertible_binary_matrix(mat: np.ndarray): def random_invertible_binary_matrix( - num_qubits: int, seed: Optional[np.random.Generator, int] = None + num_qubits: int, seed: Optional[Union[np.random.Generator, int]] = None ): """Generates a random invertible n x n binary matrix. From 5b3d04508586129450754dfd7b3b1ac98686f321 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Mon, 24 Oct 2022 18:00:35 +0300 Subject: [PATCH 31/39] fix import --- qiskit/synthesis/linear/linear_matrix_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index 034f2874de2f..f70a1374566f 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -12,8 +12,8 @@ """Utility functions for handling binary matrices.""" -import numpy as np from typing import Optional, Union +import numpy as np from qiskit.exceptions import QiskitError From 19485bfa843865e9bab6c63790692d3dc4713008 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Mon, 24 Oct 2022 18:48:51 +0300 Subject: [PATCH 32/39] fix release notes --- .../linear_function_synthesis_utils-f2f96924ca45e1fb.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml index e028a0eaa4d6..51b1d84271b7 100644 --- a/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml +++ b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml @@ -11,13 +11,13 @@ features: apply f to A^{t} and transpose the computed circuit, and to apply f to A^t^{-1} and to invert and transpose the computed circuit, thus leading to 4 generally different synthesized circuits for A, allowing to pick the best one in terms of depth or the number of gates. +upgrade: + - | + Improve code structure by moving the internal code graysynth.py from qiskit/transpiler/synthesis + to qiskit/synthesis/linear. fixes: - | Provide an internal utility function to generate a random NxN invertible binary matrix. This function is completely robust, as opposed to the previously implemented method based on computing determinant (via np.linalg.det) and checking that it's close to 1, where we already saw cases of floating point errors leading to an incorrect result (for N>100). -upgrades: - - | - Improve code structure by moving the internal code graysynth.py from qiskit/transpiler/synthesis - to qiskit/synthesis/linear. From bac7a0ba5b2b93c2d99dafb4da8861bd045ea47d Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 2 Nov 2022 14:51:40 +0200 Subject: [PATCH 33/39] renmae cnot_synth to PMH_cnot_synth --- qiskit/synthesis/linear/__init__.py | 2 +- qiskit/synthesis/linear/graysynth.py | 2 +- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index a3f4601ef66c..4a57561dba82 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -13,7 +13,7 @@ """Module containing cnot circuits and cnot-phase circuit synthesize.""" -from .graysynth import graysynth, cnot_synth +from .graysynth import graysynth, PMH_cnot_synth from .linear_matrix_utils import ( random_invertible_binary_matrix, calc_inverse_matrix, diff --git a/qiskit/synthesis/linear/graysynth.py b/qiskit/synthesis/linear/graysynth.py index 32ecfa1ecd4f..56afb397c9fe 100644 --- a/qiskit/synthesis/linear/graysynth.py +++ b/qiskit/synthesis/linear/graysynth.py @@ -180,7 +180,7 @@ def graysynth(cnots, angles, section_size=2): return qcir -def cnot_synth(state, section_size=2): +def PMH_cnot_synth(state, section_size=2): """ This function is an implementation of the Patel–Markov–Hayes algorithm for optimal synthesis of linear reversible circuits, as specified by an diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 20ceb1fc64cf..a9ed6efb9398 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -19,7 +19,7 @@ from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.transpiler.exceptions import TranspilerError from qiskit.quantum_info import decompose_clifford -from qiskit.synthesis.linear import cnot_synth +from qiskit.synthesis.linear import PMH_cnot_synth from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin @@ -168,5 +168,5 @@ class DefaultSynthesisLinearFunction(HighLevelSynthesisPlugin): def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" - decomposition = cnot_synth(high_level_object.linear) + decomposition = PMH_cnot_synth(high_level_object.linear) return decomposition From 636bcdd73722f4483135174ee8e0a1e4fa6dd597 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 2 Nov 2022 14:52:32 +0200 Subject: [PATCH 34/39] add comments following review --- qiskit/synthesis/linear/linear_circuits_utils.py | 2 ++ qiskit/synthesis/linear/linear_matrix_utils.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index b9dfa2d2260b..375c91e9e35f 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -85,9 +85,11 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b 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 ) + # 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 ) diff --git a/qiskit/synthesis/linear/linear_matrix_utils.py b/qiskit/synthesis/linear/linear_matrix_utils.py index f70a1374566f..96e00d722b33 100644 --- a/qiskit/synthesis/linear/linear_matrix_utils.py +++ b/qiskit/synthesis/linear/linear_matrix_utils.py @@ -63,7 +63,7 @@ def random_invertible_binary_matrix( def _gauss_elimination(mat, ncols=None, full_elim=False): """Gauss elimination of a matrix mat with m rows and n columns. If full_elim = True, it allows full elimination of mat[:, 0 : ncols] - Returns the matrix mat.""" + Mutates and returns the matrix mat.""" # Treat the matrix A as containing integer values mat = np.array(mat, dtype=int, copy=False) From 80c3378428fd317197df2725e9fd607f0416fc3b Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 2 Nov 2022 14:53:14 +0200 Subject: [PATCH 35/39] fix release notes following review --- ..._function_synthesis_utils-f2f96924ca45e1fb.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml index 51b1d84271b7..714df0623ea1 100644 --- a/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml +++ b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml @@ -1,20 +1,20 @@ --- features: - | - Add new internal utility functions to handle linear functions and circuits: - A utility function that checks whether a NxN binary matrix is invertible and a utility function - that computes an inverse of a NxN invertible binary matrix. - A utility function that calculates the transpose of a linear reversible circuit. - Implement the following "meta" idea for synthesizing linear functions: + Add new utility functions to handle linear functions and circuits: + * A utility function that checks whether a NxN binary matrix is invertible. + * A utility function that computes an inverse of a NxN invertible binary matrix. + * A utility function that calculates the transpose of a linear reversible circuit. + * Implement the following "meta" idea for synthesizing linear functions: given an invertible binary matrix A and a synthesis algorithm f: A -> QuantumCircuit, in addition to applying f to A, we can also apply f to A^{-1} and invert the computed circuit, - apply f to A^{t} and transpose the computed circuit, and to apply f to A^t^{-1} and to invert + apply f to A^{T} and transpose the computed circuit, and to apply f to A^{T}^{-1} and to invert and transpose the computed circuit, thus leading to 4 generally different synthesized circuits for A, allowing to pick the best one in terms of depth or the number of gates. upgrade: - | Improve code structure by moving the internal code graysynth.py from qiskit/transpiler/synthesis - to qiskit/synthesis/linear. + to qiskit/synthesis/linear, and renaming cnot_synth to PMH_cnot_synth. fixes: - | Provide an internal utility function to generate a random NxN invertible binary matrix. From 204e98b194388326807a7f8c9256baee51ac8768 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 2 Nov 2022 14:56:40 +0200 Subject: [PATCH 36/39] add a test following review --- .../python/synthesis/test_linear_synthesis.py | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index a2f3d5bc89f7..a8702197793f 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -18,7 +18,7 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import LinearFunction from qiskit.synthesis.linear import ( - cnot_synth, + PMH_cnot_synth, random_invertible_binary_matrix, check_invertible_binary_matrix, calc_inverse_matrix, @@ -40,7 +40,7 @@ def test_lnn_circuit(self): mat = LinearFunction(qc).linear for optimized in [True, False]: - optimized_qc = optimize_cx_4_options(cnot_synth, mat, optimize_count=optimized) + optimized_qc = optimize_cx_4_options(PMH_cnot_synth, mat, optimize_count=optimized) self.assertEqual(optimized_qc.depth(), 4) self.assertEqual(optimized_qc.count_ops()["cx"], 4) @@ -55,7 +55,7 @@ def test_full_circuit(self): mat = LinearFunction(qc).linear for optimized in [True, False]: - optimized_qc = optimize_cx_4_options(cnot_synth, mat, optimize_count=optimized) + optimized_qc = optimize_cx_4_options(PMH_cnot_synth, mat, optimize_count=optimized) self.assertEqual(optimized_qc.depth(), 4) self.assertEqual(optimized_qc.count_ops()["cx"], 4) @@ -63,11 +63,38 @@ def test_transpose_circ(self): """Test the transpose_cx_circ() function.""" n = 5 mat = random_invertible_binary_matrix(n, seed=1234) - qc = cnot_synth(mat) + qc = PMH_cnot_synth(mat) transposed_qc = transpose_cx_circ(qc) transposed_mat = LinearFunction(transposed_qc).linear.astype(int) self.assertTrue((mat.transpose() == transposed_mat).all()) + def test_example_circuit(self): + """Test the synthesis of an example CX circuit which provides different CX count + and depth for different optimization methods.""" + + qc = QuantumCircuit(9) + qc.swap(8, 7) + qc.swap(7, 6) + qc.cx(5, 6) + qc.cx(6, 5) + qc.swap(4, 5) + qc.cx(3, 4) + qc.cx(4, 3) + qc.swap(2, 3) + qc.cx(1, 2) + qc.cx(2, 1) + qc.cx(0, 1) + qc.cx(1, 0) + mat = LinearFunction(qc).linear + + optimized_qc = optimize_cx_4_options(PMH_cnot_synth, mat, optimize_count=True) + self.assertEqual(optimized_qc.depth(), 17) + self.assertEqual(optimized_qc.count_ops()["cx"], 20) + + optimized_qc = optimize_cx_4_options(PMH_cnot_synth, mat, optimize_count=False) + self.assertEqual(optimized_qc.depth(), 15) + self.assertEqual(optimized_qc.count_ops()["cx"], 23) + def test_invertible_matrix(self): """Test the functions for generating a random invertible matrix and inverting it.""" n = 5 From 2140cec22f16eed2dd0cd1050f3af099085c7e46 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 2 Nov 2022 15:08:14 +0200 Subject: [PATCH 37/39] rename cnot_synth to PMH_cnot_synth --- qiskit/synthesis/linear/graysynth.py | 2 +- test/python/synthesis/test_gray_synthesis.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/synthesis/linear/graysynth.py b/qiskit/synthesis/linear/graysynth.py index 56afb397c9fe..0d382c9206b4 100644 --- a/qiskit/synthesis/linear/graysynth.py +++ b/qiskit/synthesis/linear/graysynth.py @@ -176,7 +176,7 @@ def graysynth(cnots, angles, section_size=2): else: sta.append([cnots1, list(set(ilist).difference([j])), qubit]) sta.append([cnots0, list(set(ilist).difference([j])), qubit]) - qcir = qcir.compose(cnot_synth(state, section_size).inverse()) + qcir = qcir.compose(PMH_cnot_synth(state, section_size).inverse()) return qcir diff --git a/test/python/synthesis/test_gray_synthesis.py b/test/python/synthesis/test_gray_synthesis.py index cd413d7fba29..8184aa89c8f6 100644 --- a/test/python/synthesis/test_gray_synthesis.py +++ b/test/python/synthesis/test_gray_synthesis.py @@ -17,7 +17,7 @@ from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.quantum_info.operators import Operator from qiskit.extensions.unitary import UnitaryGate -from qiskit.synthesis.linear import graysynth, cnot_synth +from qiskit.synthesis.linear import graysynth, PMH_cnot_synth from qiskit.test import QiskitTestCase @@ -239,7 +239,7 @@ def test_patel_markov_hayes(self): [1, 1, 0, 1, 1, 1], [0, 0, 1, 1, 1, 0], ] - c_patel = cnot_synth(state) + c_patel = PMH_cnot_synth(state) unitary_patel = UnitaryGate(Operator(c_patel)) # Create the circuit displayed above: From b0c04c2352373c3facee1e0a0f2e2183c32447c8 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 2 Nov 2022 15:52:07 +0200 Subject: [PATCH 38/39] rename to synth_cnot_count_full_pmh --- .../library/generalized_gates/linear_function.py | 4 ++-- qiskit/synthesis/linear/__init__.py | 2 +- qiskit/synthesis/linear/graysynth.py | 4 ++-- .../passes/synthesis/high_level_synthesis.py | 4 ++-- ...ar_function_synthesis_utils-f2f96924ca45e1fb.yaml | 2 +- test/python/synthesis/test_gray_synthesis.py | 4 ++-- test/python/synthesis/test_linear_synthesis.py | 12 ++++++------ 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 9e3d71c0d869..29c26b004f91 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -134,9 +134,9 @@ def synthesize(self): Returns: QuantumCircuit: A circuit implementing the evolution. """ - from qiskit.synthesis.linear import cnot_synth + from qiskit.synthesis.linear import synth_cnot_count_full_pmh - return cnot_synth(self.linear) + return synth_cnot_count_full_pmh(self.linear) @property def linear(self): diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index 4a57561dba82..2229eda1f987 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -13,7 +13,7 @@ """Module containing cnot circuits and cnot-phase circuit synthesize.""" -from .graysynth import graysynth, PMH_cnot_synth +from .graysynth import graysynth, synth_cnot_count_full_pmh from .linear_matrix_utils import ( random_invertible_binary_matrix, calc_inverse_matrix, diff --git a/qiskit/synthesis/linear/graysynth.py b/qiskit/synthesis/linear/graysynth.py index 0d382c9206b4..c9ec516de795 100644 --- a/qiskit/synthesis/linear/graysynth.py +++ b/qiskit/synthesis/linear/graysynth.py @@ -176,11 +176,11 @@ def graysynth(cnots, angles, section_size=2): else: sta.append([cnots1, list(set(ilist).difference([j])), qubit]) sta.append([cnots0, list(set(ilist).difference([j])), qubit]) - qcir = qcir.compose(PMH_cnot_synth(state, section_size).inverse()) + qcir = qcir.compose(synth_cnot_count_full_pmh(state, section_size).inverse()) return qcir -def PMH_cnot_synth(state, section_size=2): +def synth_cnot_count_full_pmh(state, section_size=2): """ This function is an implementation of the Patel–Markov–Hayes algorithm for optimal synthesis of linear reversible circuits, as specified by an diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index a9ed6efb9398..168139d42333 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -19,7 +19,7 @@ from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.transpiler.exceptions import TranspilerError from qiskit.quantum_info import decompose_clifford -from qiskit.synthesis.linear import PMH_cnot_synth +from qiskit.synthesis.linear import synth_cnot_count_full_pmh from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin @@ -168,5 +168,5 @@ class DefaultSynthesisLinearFunction(HighLevelSynthesisPlugin): def run(self, high_level_object, **options): """Run synthesis for the given LinearFunction.""" - decomposition = PMH_cnot_synth(high_level_object.linear) + decomposition = synth_cnot_count_full_pmh(high_level_object.linear) return decomposition diff --git a/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml index 714df0623ea1..a509ab6c03ec 100644 --- a/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml +++ b/releasenotes/notes/linear_function_synthesis_utils-f2f96924ca45e1fb.yaml @@ -14,7 +14,7 @@ features: upgrade: - | Improve code structure by moving the internal code graysynth.py from qiskit/transpiler/synthesis - to qiskit/synthesis/linear, and renaming cnot_synth to PMH_cnot_synth. + to qiskit/synthesis/linear, and renaming cnot_synth to synth_cnot_count_full_pmh. fixes: - | Provide an internal utility function to generate a random NxN invertible binary matrix. diff --git a/test/python/synthesis/test_gray_synthesis.py b/test/python/synthesis/test_gray_synthesis.py index 8184aa89c8f6..738567df0d86 100644 --- a/test/python/synthesis/test_gray_synthesis.py +++ b/test/python/synthesis/test_gray_synthesis.py @@ -17,7 +17,7 @@ from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.quantum_info.operators import Operator from qiskit.extensions.unitary import UnitaryGate -from qiskit.synthesis.linear import graysynth, PMH_cnot_synth +from qiskit.synthesis.linear import graysynth, synth_cnot_count_full_pmh from qiskit.test import QiskitTestCase @@ -239,7 +239,7 @@ def test_patel_markov_hayes(self): [1, 1, 0, 1, 1, 1], [0, 0, 1, 1, 1, 0], ] - c_patel = PMH_cnot_synth(state) + c_patel = synth_cnot_count_full_pmh(state) unitary_patel = UnitaryGate(Operator(c_patel)) # Create the circuit displayed above: diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index a8702197793f..94a202d9ce8f 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -18,7 +18,7 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import LinearFunction from qiskit.synthesis.linear import ( - PMH_cnot_synth, + synth_cnot_count_full_pmh, random_invertible_binary_matrix, check_invertible_binary_matrix, calc_inverse_matrix, @@ -40,7 +40,7 @@ def test_lnn_circuit(self): mat = LinearFunction(qc).linear for optimized in [True, False]: - optimized_qc = optimize_cx_4_options(PMH_cnot_synth, mat, optimize_count=optimized) + optimized_qc = optimize_cx_4_options(synth_cnot_count_full_pmh, mat, optimize_count=optimized) self.assertEqual(optimized_qc.depth(), 4) self.assertEqual(optimized_qc.count_ops()["cx"], 4) @@ -55,7 +55,7 @@ def test_full_circuit(self): mat = LinearFunction(qc).linear for optimized in [True, False]: - optimized_qc = optimize_cx_4_options(PMH_cnot_synth, mat, optimize_count=optimized) + optimized_qc = optimize_cx_4_options(synth_cnot_count_full_pmh, mat, optimize_count=optimized) self.assertEqual(optimized_qc.depth(), 4) self.assertEqual(optimized_qc.count_ops()["cx"], 4) @@ -63,7 +63,7 @@ def test_transpose_circ(self): """Test the transpose_cx_circ() function.""" n = 5 mat = random_invertible_binary_matrix(n, seed=1234) - qc = PMH_cnot_synth(mat) + qc = synth_cnot_count_full_pmh(mat) transposed_qc = transpose_cx_circ(qc) transposed_mat = LinearFunction(transposed_qc).linear.astype(int) self.assertTrue((mat.transpose() == transposed_mat).all()) @@ -87,11 +87,11 @@ def test_example_circuit(self): qc.cx(1, 0) mat = LinearFunction(qc).linear - optimized_qc = optimize_cx_4_options(PMH_cnot_synth, 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(PMH_cnot_synth, 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 a47e03c4d93daf893d4ede20a49b3c42ca365886 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 2 Nov 2022 16:42:23 +0200 Subject: [PATCH 39/39] format tests --- test/python/synthesis/test_linear_synthesis.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/python/synthesis/test_linear_synthesis.py b/test/python/synthesis/test_linear_synthesis.py index 94a202d9ce8f..04c0ad50c5f5 100644 --- a/test/python/synthesis/test_linear_synthesis.py +++ b/test/python/synthesis/test_linear_synthesis.py @@ -40,7 +40,9 @@ def test_lnn_circuit(self): mat = LinearFunction(qc).linear for optimized in [True, False]: - optimized_qc = optimize_cx_4_options(synth_cnot_count_full_pmh, mat, optimize_count=optimized) + optimized_qc = optimize_cx_4_options( + synth_cnot_count_full_pmh, mat, optimize_count=optimized + ) self.assertEqual(optimized_qc.depth(), 4) self.assertEqual(optimized_qc.count_ops()["cx"], 4) @@ -55,7 +57,9 @@ def test_full_circuit(self): mat = LinearFunction(qc).linear for optimized in [True, False]: - optimized_qc = optimize_cx_4_options(synth_cnot_count_full_pmh, mat, optimize_count=optimized) + optimized_qc = optimize_cx_4_options( + synth_cnot_count_full_pmh, mat, optimize_count=optimized + ) self.assertEqual(optimized_qc.depth(), 4) self.assertEqual(optimized_qc.count_ops()["cx"], 4)