From 34c3c275978c13d1b123517f4c4cef841e8e4b6f Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 15 Mar 2023 13:33:01 +0000 Subject: [PATCH 01/15] transfer cnot-phase (graysynth) synthesis code to qiskit/synthesis/cnot_phase --- qiskit/synthesis/__init__.py | 2 +- qiskit/synthesis/linear/__init__.py | 2 +- qiskit/synthesis/linear/cnot_synth.py | 141 ++++++++++ qiskit/synthesis/linear/graysynth.py | 317 ---------------------- qiskit/synthesis/linear_phase/__init__.py | 1 + 5 files changed, 144 insertions(+), 319 deletions(-) create mode 100644 qiskit/synthesis/linear/cnot_synth.py delete mode 100644 qiskit/synthesis/linear/graysynth.py diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index eb3adae2955a..f6b16df3d595 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -107,7 +107,7 @@ synth_cnot_count_full_pmh, synth_cnot_depth_line_kms, ) -from .linear_phase import synth_cz_depth_line_mr +from .linear_phase import synth_cz_depth_line_mr, synth_cnot_phase_aam from .clifford import ( synth_clifford_full, synth_clifford_ag, diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index 7300ee8e6f0a..debc9474caa9 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -12,7 +12,7 @@ """Module containing cnot circuits""" -from .graysynth import graysynth, synth_cnot_count_full_pmh +from .cnot_synth import synth_cnot_count_full_pmh from .linear_depth_lnn import synth_cnot_depth_line_kms from .linear_matrix_utils import ( random_invertible_binary_matrix, diff --git a/qiskit/synthesis/linear/cnot_synth.py b/qiskit/synthesis/linear/cnot_synth.py new file mode 100644 index 000000000000..f7088fbd2c87 --- /dev/null +++ b/qiskit/synthesis/linear/cnot_synth.py @@ -0,0 +1,141 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# 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. + +""" +Implementation of the GraySynth algorithm for synthesizing CNOT-Phase +circuits with efficient CNOT cost, and the Patel-Hayes-Markov algorithm +for optimal synthesis of linear (CNOT-only) reversible circuits. +""" + +import copy +import numpy as np +from qiskit.circuit import QuantumCircuit +from qiskit.exceptions import QiskitError + + +def synth_cnot_count_full_pmh(state, section_size=2): + """ + Synthesize linear reversible circuits for all-to-all architecture + using Patel, Markov and Hayes method. + + This function is an implementation of the Patel, Markov and Hayes algorithm from [1] + for optimal synthesis of linear reversible circuits for all-to-all architecture, + as specified by an n x n matrix. + + Args: + state (list[list] or ndarray): n x n boolean invertible matrix, describing the state + of the input circuit + section_size (int): the size of each section, used in the + Patel–Markov–Hayes algorithm [1]. section_size must be a factor of num_qubits. + + Returns: + QuantumCircuit: a CX-only circuit implementing the linear transformation. + + Raises: + QiskitError: when variable "state" isn't of type numpy.ndarray + + References: + 1. Patel, Ketan N., Igor L. Markov, and John P. Hayes, + *Optimal synthesis of linear reversible circuits*, + Quantum Information & Computation 8.3 (2008): 282-294. + `arXiv:quant-ph/0302002 [quant-ph] `_ + """ + if not isinstance(state, (list, np.ndarray)): + raise QiskitError( + "state should be of type list or numpy.ndarray, " + "but was of the type {}".format(type(state)) + ) + state = np.array(state) + # Synthesize lower triangular part + [state, circuit_l] = _lwr_cnot_synth(state, section_size) + state = np.transpose(state) + # Synthesize upper triangular part + [state, circuit_u] = _lwr_cnot_synth(state, section_size) + circuit_l.reverse() + for i in circuit_u: + i.reverse() + # Convert the list into a circuit of C-NOT gates + circ = QuantumCircuit(state.shape[0]) + for i in circuit_u + circuit_l: + circ.cx(i[0], i[1]) + return circ + + +def _lwr_cnot_synth(state, section_size): + """ + This function is a helper function of the algorithm for optimal synthesis + of linear reversible circuits (the Patel–Markov–Hayes algorithm). It works + like gaussian elimination, except that it works a lot faster, and requires + fewer steps (and therefore fewer CNOTs). It takes the matrix "state" and + splits it into sections of size section_size. Then it eliminates all non-zero + sub-rows within each section, which are the same as a non-zero sub-row + above. Once this has been done, it continues with normal gaussian elimination. + The benefit is that with small section sizes (m), most of the sub-rows will + be cleared in the first step, resulting in a factor m fewer row row operations + during Gaussian elimination. + + The algorithm is described in detail in the following paper + "Optimal synthesis of linear reversible circuits." + Patel, Ketan N., Igor L. Markov, and John P. Hayes. + Quantum Information & Computation 8.3 (2008): 282-294. + + Note: + This implementation tweaks the Patel, Markov, and Hayes algorithm by adding + a "back reduce" which adds rows below the pivot row with a high degree of + overlap back to it. The intuition is to avoid a high-weight pivot row + increasing the weight of lower rows. + + Args: + state (ndarray): n x n matrix, describing a linear quantum circuit + section_size (int): the section size the matrix columns are divided into + + Returns: + numpy.matrix: n by n matrix, describing the state of the output circuit + list: a k by 2 list of C-NOT operations that need to be applied + """ + circuit = [] + num_qubits = state.shape[0] + cutoff = 1 + + # Iterate over column sections + for sec in range(1, int(np.floor(num_qubits / section_size) + 1)): + # Remove duplicate sub-rows in section sec + patt = {} + for row in range((sec - 1) * section_size, num_qubits): + sub_row_patt = copy.deepcopy(state[row, (sec - 1) * section_size : sec * section_size]) + if np.sum(sub_row_patt) == 0: + continue + if str(sub_row_patt) not in patt: + patt[str(sub_row_patt)] = row + else: + state[row, :] ^= state[patt[str(sub_row_patt)], :] + circuit.append([patt[str(sub_row_patt)], row]) + # Use gaussian elimination for remaining entries in column section + for col in range((sec - 1) * section_size, sec * section_size): + # Check if 1 on diagonal + diag_one = 1 + if state[col, col] == 0: + diag_one = 0 + # Remove ones in rows below column col + for row in range(col + 1, num_qubits): + if state[row, col] == 1: + if diag_one == 0: + state[col, :] ^= state[row, :] + circuit.append([row, col]) + diag_one = 1 + state[row, :] ^= state[col, :] + circuit.append([col, row]) + # Back reduce the pivot row using the current row + if sum(state[col, :] & state[row, :]) > cutoff: + state[col, :] ^= state[row, :] + circuit.append([row, col]) + return [state, circuit] diff --git a/qiskit/synthesis/linear/graysynth.py b/qiskit/synthesis/linear/graysynth.py deleted file mode 100644 index 9caf7d45ae66..000000000000 --- a/qiskit/synthesis/linear/graysynth.py +++ /dev/null @@ -1,317 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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. - -""" -Implementation of the GraySynth algorithm for synthesizing CNOT-Phase -circuits with efficient CNOT cost, and the Patel-Hayes-Markov algorithm -for optimal synthesis of linear (CNOT-only) reversible circuits. -""" - -import copy -import numpy as np -from qiskit.circuit import QuantumCircuit -from qiskit.exceptions import QiskitError - - -def graysynth(cnots, angles, section_size=2): - """This function is an implementation of the GraySynth algorithm. - - GraySynth is a heuristic algorithm for synthesizing small parity networks. - It is inspired by Gray codes. Given a set of binary strings S - (called "cnots" bellow), the algorithm synthesizes a parity network for S by - repeatedly choosing an index i to expand and then effectively recursing on - the co-factors S_0 and S_1, consisting of the strings y in S, - with y_i = 0 or 1 respectively. As a subset S is recursively expanded, - CNOT gates are applied so that a designated target bit contains the - (partial) parity ksi_y(x) where y_i = 1 if and only if y'_i = 1 for for all - y' in S. If S is a singleton {y'}, then y = y', hence the target bit contains - the value ksi_y'(x) as desired. - - Notably, rather than uncomputing this sequence of CNOT gates when a subset S - is finished being synthesized, the algorithm maintains the invariant - that the remaining parities to be computed are expressed over the current state - of bits. This allows the algorithm to avoid the 'backtracking' inherent in - uncomputing-based methods. - - The algorithm is described in detail in the following paper in section 4: - "On the controlled-NOT complexity of controlled-NOT–phase circuits." - Amy, Matthew, Parsiad Azimzadeh, and Michele Mosca. - Quantum Science and Technology 4.1 (2018): 015002. - - Args: - cnots (list[list]): a matrix whose columns are the parities to be synthesized - e.g.:: - - [[0, 1, 1, 1, 1, 1], - [1, 0, 0, 1, 1, 1], - [1, 0, 0, 1, 0, 0], - [0, 0, 1, 0, 1, 0]] - - corresponds to:: - - x1^x2 + x0 + x0^x3 + x0^x1^x2 + x0^x1^x3 + x0^x1 - - angles (list): a list containing all the phase-shift gates which are - to be applied, in the same order as in "cnots". A number is - interpreted as the angle of u1(angle), otherwise the elements - have to be 't', 'tdg', 's', 'sdg' or 'z'. - - section_size (int): the size of every section, used in _lwr_cnot_synth(), in the - Patel–Markov–Hayes algorithm. section_size must be a factor of num_qubits. - - Returns: - QuantumCircuit: the quantum circuit - - Raises: - QiskitError: when dimensions of cnots and angles don't align - """ - num_qubits = len(cnots) - - # Create a quantum circuit on num_qubits - qcir = QuantumCircuit(num_qubits) - - if len(cnots[0]) != len(angles): - raise QiskitError('Size of "cnots" and "angles" do not match.') - - range_list = list(range(num_qubits)) - epsilon = num_qubits - sta = [] - cnots_copy = np.transpose(np.array(copy.deepcopy(cnots))) - # This matrix keeps track of the state in the algorithm - state = np.eye(num_qubits).astype("int") - - # Check if some phase-shift gates can be applied, before adding any C-NOT gates - for qubit in range(num_qubits): - index = 0 - for icnots in cnots_copy: - if np.array_equal(icnots, state[qubit]): - if angles[index] == "t": - qcir.t(qubit) - elif angles[index] == "tdg": - qcir.tdg(qubit) - elif angles[index] == "s": - qcir.s(qubit) - elif angles[index] == "sdg": - qcir.sdg(qubit) - elif angles[index] == "z": - qcir.z(qubit) - else: - qcir.p(angles[index] % np.pi, qubit) - del angles[index] - cnots_copy = np.delete(cnots_copy, index, axis=0) - if index == len(cnots_copy): - break - index -= 1 - index += 1 - - # Implementation of the pseudo-code (Algorithm 1) in the aforementioned paper - sta.append([cnots, range_list, epsilon]) - while sta != []: - [cnots, ilist, qubit] = sta.pop() - if cnots == []: - continue - if 0 <= qubit < num_qubits: - condition = True - while condition: - condition = False - for j in range(num_qubits): - if (j != qubit) and (sum(cnots[j]) == len(cnots[j])): - condition = True - qcir.cx(j, qubit) - state[qubit] ^= state[j] - index = 0 - for icnots in cnots_copy: - if np.array_equal(icnots, state[qubit]): - if angles[index] == "t": - qcir.t(qubit) - elif angles[index] == "tdg": - qcir.tdg(qubit) - elif angles[index] == "s": - qcir.s(qubit) - elif angles[index] == "sdg": - qcir.sdg(qubit) - elif angles[index] == "z": - qcir.z(qubit) - else: - qcir.p(angles[index] % np.pi, qubit) - del angles[index] - cnots_copy = np.delete(cnots_copy, index, axis=0) - if index == len(cnots_copy): - break - index -= 1 - index += 1 - for x in _remove_duplicates(sta + [[cnots, ilist, qubit]]): - [cnotsp, _, _] = x - if cnotsp == []: - continue - for ttt in range(len(cnotsp[j])): - cnotsp[j][ttt] ^= cnotsp[qubit][ttt] - if ilist == []: - continue - # See line 18 in pseudo-code of Algorithm 1 in the aforementioned paper - # this choice of j maximizes the size of the largest subset (S_0 or S_1) - # and the larger a subset, the closer it gets to the ideal in the - # Gray code of one CNOT per string. - j = ilist[np.argmax([[max(row.count(0), row.count(1)) for row in cnots][k] for k in ilist])] - cnots0 = [] - cnots1 = [] - for y in list(map(list, zip(*cnots))): - if y[j] == 0: - cnots0.append(y) - elif y[j] == 1: - cnots1.append(y) - cnots0 = list(map(list, zip(*cnots0))) - cnots1 = list(map(list, zip(*cnots1))) - if qubit == epsilon: - sta.append([cnots1, list(set(ilist).difference([j])), j]) - else: - sta.append([cnots1, list(set(ilist).difference([j])), qubit]) - sta.append([cnots0, list(set(ilist).difference([j])), qubit]) - qcir &= synth_cnot_count_full_pmh(state, section_size).inverse() - return qcir - - -def synth_cnot_count_full_pmh(state, section_size=2): - """ - Synthesize linear reversible circuits for all-to-all architecture - using Patel, Markov and Hayes method. - - This function is an implementation of the Patel, Markov and Hayes algorithm from [1] - for optimal synthesis of linear reversible circuits for all-to-all architecture, - as specified by an n x n matrix. - - Args: - state (list[list] or ndarray): n x n boolean invertible matrix, describing the state - of the input circuit - section_size (int): the size of each section, used in the - Patel–Markov–Hayes algorithm [1]. section_size must be a factor of num_qubits. - - Returns: - QuantumCircuit: a CX-only circuit implementing the linear transformation. - - Raises: - QiskitError: when variable "state" isn't of type numpy.ndarray - - References: - 1. Patel, Ketan N., Igor L. Markov, and John P. Hayes, - *Optimal synthesis of linear reversible circuits*, - Quantum Information & Computation 8.3 (2008): 282-294. - `arXiv:quant-ph/0302002 [quant-ph] `_ - """ - if not isinstance(state, (list, np.ndarray)): - raise QiskitError( - "state should be of type list or numpy.ndarray, " - "but was of the type {}".format(type(state)) - ) - state = np.array(state) - # Synthesize lower triangular part - [state, circuit_l] = _lwr_cnot_synth(state, section_size) - state = np.transpose(state) - # Synthesize upper triangular part - [state, circuit_u] = _lwr_cnot_synth(state, section_size) - circuit_l.reverse() - for i in circuit_u: - i.reverse() - # Convert the list into a circuit of C-NOT gates - circ = QuantumCircuit(state.shape[0]) - for i in circuit_u + circuit_l: - circ.cx(i[0], i[1]) - return circ - - -def _lwr_cnot_synth(state, section_size): - """ - This function is a helper function of the algorithm for optimal synthesis - of linear reversible circuits (the Patel–Markov–Hayes algorithm). It works - like gaussian elimination, except that it works a lot faster, and requires - fewer steps (and therefore fewer CNOTs). It takes the matrix "state" and - splits it into sections of size section_size. Then it eliminates all non-zero - sub-rows within each section, which are the same as a non-zero sub-row - above. Once this has been done, it continues with normal gaussian elimination. - The benefit is that with small section sizes (m), most of the sub-rows will - be cleared in the first step, resulting in a factor m fewer row row operations - during Gaussian elimination. - - The algorithm is described in detail in the following paper - "Optimal synthesis of linear reversible circuits." - Patel, Ketan N., Igor L. Markov, and John P. Hayes. - Quantum Information & Computation 8.3 (2008): 282-294. - - Note: - This implementation tweaks the Patel, Markov, and Hayes algorithm by adding - a "back reduce" which adds rows below the pivot row with a high degree of - overlap back to it. The intuition is to avoid a high-weight pivot row - increasing the weight of lower rows. - - Args: - state (ndarray): n x n matrix, describing a linear quantum circuit - section_size (int): the section size the matrix columns are divided into - - Returns: - numpy.matrix: n by n matrix, describing the state of the output circuit - list: a k by 2 list of C-NOT operations that need to be applied - """ - circuit = [] - num_qubits = state.shape[0] - cutoff = 1 - - # Iterate over column sections - for sec in range(1, int(np.floor(num_qubits / section_size) + 1)): - # Remove duplicate sub-rows in section sec - patt = {} - for row in range((sec - 1) * section_size, num_qubits): - sub_row_patt = copy.deepcopy(state[row, (sec - 1) * section_size : sec * section_size]) - if np.sum(sub_row_patt) == 0: - continue - if str(sub_row_patt) not in patt: - patt[str(sub_row_patt)] = row - else: - state[row, :] ^= state[patt[str(sub_row_patt)], :] - circuit.append([patt[str(sub_row_patt)], row]) - # Use gaussian elimination for remaining entries in column section - for col in range((sec - 1) * section_size, sec * section_size): - # Check if 1 on diagonal - diag_one = 1 - if state[col, col] == 0: - diag_one = 0 - # Remove ones in rows below column col - for row in range(col + 1, num_qubits): - if state[row, col] == 1: - if diag_one == 0: - state[col, :] ^= state[row, :] - circuit.append([row, col]) - diag_one = 1 - state[row, :] ^= state[col, :] - circuit.append([col, row]) - # Back reduce the pivot row using the current row - if sum(state[col, :] & state[row, :]) > cutoff: - state[col, :] ^= state[row, :] - circuit.append([row, col]) - return [state, circuit] - - -def _remove_duplicates(lists): - """ - Remove duplicates in list - - Args: - lists (list): a list which may contain duplicate elements. - - Returns: - list: a list which contains only unique elements. - """ - - unique_list = [] - for element in lists: - if element not in unique_list: - unique_list.append(element) - return unique_list diff --git a/qiskit/synthesis/linear_phase/__init__.py b/qiskit/synthesis/linear_phase/__init__.py index 78ce4433603f..0be6c9c91175 100644 --- a/qiskit/synthesis/linear_phase/__init__.py +++ b/qiskit/synthesis/linear_phase/__init__.py @@ -13,3 +13,4 @@ """Module containing cnot-phase circuits""" from .cz_depth_lnn import synth_cz_depth_line_mr +from .cnot_phase_synth import synth_cnot_phase_aam From 550879682676e88b67fc7513ca1e7e83d07fcd23 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 15 Mar 2023 13:34:14 +0000 Subject: [PATCH 02/15] prepare qiskit/transpiler/graysynth.py for deprecation --- qiskit/transpiler/synthesis/__init__.py | 11 +---------- qiskit/transpiler/synthesis/graysynth.py | 8 +++++++- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/qiskit/transpiler/synthesis/__init__.py b/qiskit/transpiler/synthesis/__init__.py index 62dcdfc0c74c..b8ac116d2284 100644 --- a/qiskit/transpiler/synthesis/__init__.py +++ b/qiskit/transpiler/synthesis/__init__.py @@ -10,16 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# pylint: disable=undefined-all-variable """Module containing transpiler synthesize.""" -import importlib - -__all__ = ["graysynth", "cnot_synth"] - - -def __getattr__(name): - if name in __all__: - return getattr(importlib.import_module(".graysynth", __name__), name) - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") +from .graysynth import graysynth, cnot_synth diff --git a/qiskit/transpiler/synthesis/graysynth.py b/qiskit/transpiler/synthesis/graysynth.py index 249335d27a73..3a9fe9987f22 100644 --- a/qiskit/transpiler/synthesis/graysynth.py +++ b/qiskit/transpiler/synthesis/graysynth.py @@ -21,7 +21,8 @@ # Redirect getattrs to modules new location # TODO: Deprecate in 0.24.0 and remove in 0.26.0 -from qiskit.synthesis.linear.graysynth import * +from qiskit.synthesis.linear.cnot_synth import * +from qiskit.synthesis.linear_phase.cnot_phase_synth import * def cnot_synth(state, section_size=2): @@ -52,3 +53,8 @@ def cnot_synth(state, section_size=2): `arXiv:quant-ph/0302002 [quant-ph] `_ """ return synth_cnot_count_full_pmh(state, section_size=section_size) + + +def graysynth(cnots, angles, section_size=2): + """Synthesize a cnot-phase circuit.""" + return synth_cnot_phase_aam(cnots, angles, section_size=section_size) From 6ba9d73af032c5aaf6a01cb5a70d5e9e7948867e Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 15 Mar 2023 13:34:49 +0000 Subject: [PATCH 03/15] update test_gray_synthesis --- test/python/synthesis/test_gray_synthesis.py | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/test/python/synthesis/test_gray_synthesis.py b/test/python/synthesis/test_gray_synthesis.py index 3d2ffbb38ccd..cd3558c68f6d 100644 --- a/test/python/synthesis/test_gray_synthesis.py +++ b/test/python/synthesis/test_gray_synthesis.py @@ -19,9 +19,12 @@ 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, synth_cnot_count_full_pmh -from qiskit.transpiler.synthesis import cnot_synth # pylint: disable=no-name-in-module -from qiskit.transpiler.synthesis.graysynth import graysynth as legacy_graysynth +from qiskit.synthesis.linear import synth_cnot_count_full_pmh +from qiskit.synthesis.linear_phase import synth_cnot_phase_aam +from qiskit.transpiler.synthesis.graysynth import ( + cnot_synth, + graysynth, +) # pylint: disable=no-name-in-module from qiskit.test import QiskitTestCase @@ -29,7 +32,7 @@ class TestGraySynth(QiskitTestCase): """Test the Gray-Synth algorithm.""" - @ddt.data(graysynth, legacy_graysynth) + @ddt.data(synth_cnot_phase_aam, graysynth) def test_gray_synth(self, synth_func): """Test synthesis of a small parity network via gray_synth. @@ -101,7 +104,8 @@ def test_gray_synth(self, synth_func): # Check if the two circuits are equivalent self.assertEqual(unitary_gray, unitary_compare) - def test_paper_example(self): + @ddt.data(synth_cnot_phase_aam, graysynth) + def test_paper_example(self, synth_func): """Test synthesis of a diagonal operator from the paper. The diagonal operator in Example 4.2 @@ -129,7 +133,7 @@ def test_paper_example(self): """ cnots = [[0, 1, 1, 1, 1, 1], [1, 0, 0, 1, 1, 1], [1, 0, 0, 1, 0, 0], [0, 0, 1, 0, 1, 0]] angles = ["t"] * 6 - c_gray = graysynth(cnots, angles) + c_gray = synth_func(cnots, angles) unitary_gray = UnitaryGate(Operator(c_gray)) # Create the circuit displayed above: @@ -155,7 +159,8 @@ def test_paper_example(self): # Check if the two circuits are equivalent self.assertEqual(unitary_gray, unitary_compare) - def test_ccz(self): + @ddt.data(synth_cnot_phase_aam, graysynth) + def test_ccz(self, synth_func): """Test synthesis of the doubly-controlled Z gate. The diagonal operator in Example 4.3 @@ -181,7 +186,7 @@ def test_ccz(self): """ cnots = [[1, 0, 0, 1, 1, 0, 1], [0, 1, 0, 1, 0, 1, 1], [0, 0, 1, 0, 1, 1, 1]] angles = ["t", "t", "t", "tdg", "tdg", "tdg", "t"] - c_gray = graysynth(cnots, angles) + c_gray = synth_func(cnots, angles) unitary_gray = UnitaryGate(Operator(c_gray)) # Create the circuit displayed above: From 8d8534bc3240f757739b399622d58cc1af8aa819 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 15 Mar 2023 13:44:52 +0000 Subject: [PATCH 04/15] add cnot-phase (graysynth) code --- .../linear_phase/cnot_phase_synth.py | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 qiskit/synthesis/linear_phase/cnot_phase_synth.py diff --git a/qiskit/synthesis/linear_phase/cnot_phase_synth.py b/qiskit/synthesis/linear_phase/cnot_phase_synth.py new file mode 100644 index 000000000000..769800bc2588 --- /dev/null +++ b/qiskit/synthesis/linear_phase/cnot_phase_synth.py @@ -0,0 +1,199 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# 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. + +""" +Implementation of the GraySynth algorithm for synthesizing CNOT-Phase +circuits with efficient CNOT cost, and the Patel-Hayes-Markov algorithm +for optimal synthesis of linear (CNOT-only) reversible circuits. +""" + +import copy +import numpy as np +from qiskit.circuit import QuantumCircuit +from qiskit.exceptions import QiskitError +from qiskit.synthesis.linear import synth_cnot_count_full_pmh + + +def synth_cnot_phase_aam(cnots, angles, section_size=2): + """This function is an implementation of the GraySynth algorithm. + + GraySynth is a heuristic algorithm for synthesizing small parity networks. + It is inspired by Gray codes. Given a set of binary strings S + (called "cnots" bellow), the algorithm synthesizes a parity network for S by + repeatedly choosing an index i to expand and then effectively recursing on + the co-factors S_0 and S_1, consisting of the strings y in S, + with y_i = 0 or 1 respectively. As a subset S is recursively expanded, + CNOT gates are applied so that a designated target bit contains the + (partial) parity ksi_y(x) where y_i = 1 if and only if y'_i = 1 for for all + y' in S. If S is a singleton {y'}, then y = y', hence the target bit contains + the value ksi_y'(x) as desired. + + Notably, rather than uncomputing this sequence of CNOT gates when a subset S + is finished being synthesized, the algorithm maintains the invariant + that the remaining parities to be computed are expressed over the current state + of bits. This allows the algorithm to avoid the 'backtracking' inherent in + uncomputing-based methods. + + The algorithm is described in detail in the following paper in section 4: + "On the controlled-NOT complexity of controlled-NOT–phase circuits." + Amy, Matthew, Parsiad Azimzadeh, and Michele Mosca. + Quantum Science and Technology 4.1 (2018): 015002. + + Args: + cnots (list[list]): a matrix whose columns are the parities to be synthesized + e.g.:: + + [[0, 1, 1, 1, 1, 1], + [1, 0, 0, 1, 1, 1], + [1, 0, 0, 1, 0, 0], + [0, 0, 1, 0, 1, 0]] + + corresponds to:: + + x1^x2 + x0 + x0^x3 + x0^x1^x2 + x0^x1^x3 + x0^x1 + + angles (list): a list containing all the phase-shift gates which are + to be applied, in the same order as in "cnots". A number is + interpreted as the angle of u1(angle), otherwise the elements + have to be 't', 'tdg', 's', 'sdg' or 'z'. + + section_size (int): the size of every section, used in _lwr_cnot_synth(), in the + Patel–Markov–Hayes algorithm. section_size must be a factor of num_qubits. + + Returns: + QuantumCircuit: the quantum circuit + + Raises: + QiskitError: when dimensions of cnots and angles don't align + """ + num_qubits = len(cnots) + + # Create a quantum circuit on num_qubits + qcir = QuantumCircuit(num_qubits) + + if len(cnots[0]) != len(angles): + raise QiskitError('Size of "cnots" and "angles" do not match.') + + range_list = list(range(num_qubits)) + epsilon = num_qubits + sta = [] + cnots_copy = np.transpose(np.array(copy.deepcopy(cnots))) + # This matrix keeps track of the state in the algorithm + state = np.eye(num_qubits).astype("int") + + # Check if some phase-shift gates can be applied, before adding any C-NOT gates + for qubit in range(num_qubits): + index = 0 + for icnots in cnots_copy: + if np.array_equal(icnots, state[qubit]): + if angles[index] == "t": + qcir.t(qubit) + elif angles[index] == "tdg": + qcir.tdg(qubit) + elif angles[index] == "s": + qcir.s(qubit) + elif angles[index] == "sdg": + qcir.sdg(qubit) + elif angles[index] == "z": + qcir.z(qubit) + else: + qcir.p(angles[index] % np.pi, qubit) + del angles[index] + cnots_copy = np.delete(cnots_copy, index, axis=0) + if index == len(cnots_copy): + break + index -= 1 + index += 1 + + # Implementation of the pseudo-code (Algorithm 1) in the aforementioned paper + sta.append([cnots, range_list, epsilon]) + while sta != []: + [cnots, ilist, qubit] = sta.pop() + if cnots == []: + continue + if 0 <= qubit < num_qubits: + condition = True + while condition: + condition = False + for j in range(num_qubits): + if (j != qubit) and (sum(cnots[j]) == len(cnots[j])): + condition = True + qcir.cx(j, qubit) + state[qubit] ^= state[j] + index = 0 + for icnots in cnots_copy: + if np.array_equal(icnots, state[qubit]): + if angles[index] == "t": + qcir.t(qubit) + elif angles[index] == "tdg": + qcir.tdg(qubit) + elif angles[index] == "s": + qcir.s(qubit) + elif angles[index] == "sdg": + qcir.sdg(qubit) + elif angles[index] == "z": + qcir.z(qubit) + else: + qcir.p(angles[index] % np.pi, qubit) + del angles[index] + cnots_copy = np.delete(cnots_copy, index, axis=0) + if index == len(cnots_copy): + break + index -= 1 + index += 1 + for x in _remove_duplicates(sta + [[cnots, ilist, qubit]]): + [cnotsp, _, _] = x + if cnotsp == []: + continue + for ttt in range(len(cnotsp[j])): + cnotsp[j][ttt] ^= cnotsp[qubit][ttt] + if ilist == []: + continue + # See line 18 in pseudo-code of Algorithm 1 in the aforementioned paper + # this choice of j maximizes the size of the largest subset (S_0 or S_1) + # and the larger a subset, the closer it gets to the ideal in the + # Gray code of one CNOT per string. + j = ilist[np.argmax([[max(row.count(0), row.count(1)) for row in cnots][k] for k in ilist])] + cnots0 = [] + cnots1 = [] + for y in list(map(list, zip(*cnots))): + if y[j] == 0: + cnots0.append(y) + elif y[j] == 1: + cnots1.append(y) + cnots0 = list(map(list, zip(*cnots0))) + cnots1 = list(map(list, zip(*cnots1))) + if qubit == epsilon: + sta.append([cnots1, list(set(ilist).difference([j])), j]) + else: + sta.append([cnots1, list(set(ilist).difference([j])), qubit]) + sta.append([cnots0, list(set(ilist).difference([j])), qubit]) + qcir &= synth_cnot_count_full_pmh(state, section_size).inverse() + return qcir + + +def _remove_duplicates(lists): + """ + Remove duplicates in list + + Args: + lists (list): a list which may contain duplicate elements. + + Returns: + list: a list which contains only unique elements. + """ + + unique_list = [] + for element in lists: + if element not in unique_list: + unique_list.append(element) + return unique_list From c33e114175ed6b29556a5960d72d917b979fbfa4 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 29 Mar 2023 09:37:22 +0000 Subject: [PATCH 05/15] update docstring of cnot_phase_synth --- .../linear_phase/cnot_phase_synth.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/qiskit/synthesis/linear_phase/cnot_phase_synth.py b/qiskit/synthesis/linear_phase/cnot_phase_synth.py index 769800bc2588..21a4a0b5de37 100644 --- a/qiskit/synthesis/linear_phase/cnot_phase_synth.py +++ b/qiskit/synthesis/linear_phase/cnot_phase_synth.py @@ -24,16 +24,17 @@ def synth_cnot_phase_aam(cnots, angles, section_size=2): - """This function is an implementation of the GraySynth algorithm. + """This function is an implementation of the GraySynth algorithm of + Amy, Azimadeh and Mosca. - GraySynth is a heuristic algorithm for synthesizing small parity networks. + GraySynth is a heuristic algorithm from [1] for synthesizing small parity networks. It is inspired by Gray codes. Given a set of binary strings S (called "cnots" bellow), the algorithm synthesizes a parity network for S by repeatedly choosing an index i to expand and then effectively recursing on the co-factors S_0 and S_1, consisting of the strings y in S, with y_i = 0 or 1 respectively. As a subset S is recursively expanded, CNOT gates are applied so that a designated target bit contains the - (partial) parity ksi_y(x) where y_i = 1 if and only if y'_i = 1 for for all + (partial) parity ksi_y(x) where y_i = 1 if and only if y'_i = 1 for all y' in S. If S is a singleton {y'}, then y = y', hence the target bit contains the value ksi_y'(x) as desired. @@ -43,10 +44,7 @@ def synth_cnot_phase_aam(cnots, angles, section_size=2): of bits. This allows the algorithm to avoid the 'backtracking' inherent in uncomputing-based methods. - The algorithm is described in detail in the following paper in section 4: - "On the controlled-NOT complexity of controlled-NOT–phase circuits." - Amy, Matthew, Parsiad Azimzadeh, and Michele Mosca. - Quantum Science and Technology 4.1 (2018): 015002. + The algorithm is described in detail in section 4 of [1]. Args: cnots (list[list]): a matrix whose columns are the parities to be synthesized @@ -63,17 +61,23 @@ def synth_cnot_phase_aam(cnots, angles, section_size=2): angles (list): a list containing all the phase-shift gates which are to be applied, in the same order as in "cnots". A number is - interpreted as the angle of u1(angle), otherwise the elements + interpreted as the angle of p(angle), otherwise the elements have to be 't', 'tdg', 's', 'sdg' or 'z'. section_size (int): the size of every section, used in _lwr_cnot_synth(), in the Patel–Markov–Hayes algorithm. section_size must be a factor of num_qubits. Returns: - QuantumCircuit: the quantum circuit + QuantumCircuit: the decomposed quantum circuit. Raises: - QiskitError: when dimensions of cnots and angles don't align + QiskitError: when dimensions of cnots and angles don't align. + + References: + 1. Amy, Matthew, Parsiad Azimzadeh, and Michele Mosca. + *On the controlled-NOT complexity of controlled-NOT–phase circuits.*, + Quantum Science and Technology 4.1 (2018): 015002. + `arXiv:1712.01859 `_ """ num_qubits = len(cnots) From 94da986706662cde8ad709fef7480c95aafd1396 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 30 Mar 2023 08:03:34 +0000 Subject: [PATCH 06/15] add deprecation functions --- qiskit/transpiler/synthesis/graysynth.py | 9 +++++++-- ...st_gray_synthesis.py => test_cnot_phase_synthesis.py} | 0 2 files changed, 7 insertions(+), 2 deletions(-) rename test/python/synthesis/{test_gray_synthesis.py => test_cnot_phase_synthesis.py} (100%) diff --git a/qiskit/transpiler/synthesis/graysynth.py b/qiskit/transpiler/synthesis/graysynth.py index 3a9fe9987f22..2e6c85e1c068 100644 --- a/qiskit/transpiler/synthesis/graysynth.py +++ b/qiskit/transpiler/synthesis/graysynth.py @@ -23,8 +23,11 @@ # TODO: Deprecate in 0.24.0 and remove in 0.26.0 from qiskit.synthesis.linear.cnot_synth import * from qiskit.synthesis.linear_phase.cnot_phase_synth import * +from qiskit.utils.deprecation import deprecate_func - +@deprecate_func( + additional_msg="Instead, use the function qiskit.synthesis.synth_cnot_count_full_pmh.", since="0.24.0" +) def cnot_synth(state, section_size=2): """ Synthesize linear reversible circuits for all-to-all architecture @@ -54,7 +57,9 @@ def cnot_synth(state, section_size=2): """ return synth_cnot_count_full_pmh(state, section_size=section_size) - +@deprecate_func( + additional_msg="Instead, use the function qiskit.synthesis.synth_cnot_phase_aam.", since="0.24.0" +) def graysynth(cnots, angles, section_size=2): """Synthesize a cnot-phase circuit.""" return synth_cnot_phase_aam(cnots, angles, section_size=section_size) diff --git a/test/python/synthesis/test_gray_synthesis.py b/test/python/synthesis/test_cnot_phase_synthesis.py similarity index 100% rename from test/python/synthesis/test_gray_synthesis.py rename to test/python/synthesis/test_cnot_phase_synthesis.py From b8a005ce45707b9b62f96447cd2c9bcb3d33eead Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 30 Mar 2023 08:05:12 +0000 Subject: [PATCH 07/15] fix tests following deprecations --- .../circuit/library/test_linear_function.py | 2 +- .../synthesis/test_cnot_phase_synthesis.py | 24 +++++++------------ 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/test/python/circuit/library/test_linear_function.py b/test/python/circuit/library/test_linear_function.py index b7ca592ace80..f9b7e5dce21e 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_gray_synthesis.py + # This code is adapted from test_cnot_phase_synthesis.py binary_matrix = [ [1, 1, 0, 0, 0, 0], [1, 0, 0, 1, 1, 0], diff --git a/test/python/synthesis/test_cnot_phase_synthesis.py b/test/python/synthesis/test_cnot_phase_synthesis.py index cd3558c68f6d..ee257c543023 100644 --- a/test/python/synthesis/test_cnot_phase_synthesis.py +++ b/test/python/synthesis/test_cnot_phase_synthesis.py @@ -21,10 +21,6 @@ from qiskit.extensions.unitary import UnitaryGate from qiskit.synthesis.linear import synth_cnot_count_full_pmh from qiskit.synthesis.linear_phase import synth_cnot_phase_aam -from qiskit.transpiler.synthesis.graysynth import ( - cnot_synth, - graysynth, -) # pylint: disable=no-name-in-module from qiskit.test import QiskitTestCase @@ -32,8 +28,7 @@ class TestGraySynth(QiskitTestCase): """Test the Gray-Synth algorithm.""" - @ddt.data(synth_cnot_phase_aam, graysynth) - def test_gray_synth(self, synth_func): + def test_gray_synth(self): """Test synthesis of a small parity network via gray_synth. The algorithm should take the following matrix as an input: @@ -75,7 +70,7 @@ def test_gray_synth(self, synth_func): [0, 1, 0, 0, 1, 0], ] angles = ["s", "t", "z", "s", "t", "t"] - c_gray = synth_func(cnots, angles) + c_gray = synth_cnot_phase_aam(cnots, angles) unitary_gray = UnitaryGate(Operator(c_gray)) # Create the circuit displayed above: @@ -104,8 +99,7 @@ def test_gray_synth(self, synth_func): # Check if the two circuits are equivalent self.assertEqual(unitary_gray, unitary_compare) - @ddt.data(synth_cnot_phase_aam, graysynth) - def test_paper_example(self, synth_func): + def test_paper_example(self): """Test synthesis of a diagonal operator from the paper. The diagonal operator in Example 4.2 @@ -133,7 +127,7 @@ def test_paper_example(self, synth_func): """ cnots = [[0, 1, 1, 1, 1, 1], [1, 0, 0, 1, 1, 1], [1, 0, 0, 1, 0, 0], [0, 0, 1, 0, 1, 0]] angles = ["t"] * 6 - c_gray = synth_func(cnots, angles) + c_gray = synth_cnot_phase_aam(cnots, angles) unitary_gray = UnitaryGate(Operator(c_gray)) # Create the circuit displayed above: @@ -159,8 +153,7 @@ def test_paper_example(self, synth_func): # Check if the two circuits are equivalent self.assertEqual(unitary_gray, unitary_compare) - @ddt.data(synth_cnot_phase_aam, graysynth) - def test_ccz(self, synth_func): + def test_ccz(self): """Test synthesis of the doubly-controlled Z gate. The diagonal operator in Example 4.3 @@ -186,7 +179,7 @@ def test_ccz(self, synth_func): """ cnots = [[1, 0, 0, 1, 1, 0, 1], [0, 1, 0, 1, 0, 1, 1], [0, 0, 1, 0, 1, 1, 1]] angles = ["t", "t", "t", "tdg", "tdg", "tdg", "t"] - c_gray = synth_func(cnots, angles) + c_gray = synth_cnot_phase_aam(cnots, angles) unitary_gray = UnitaryGate(Operator(c_gray)) # Create the circuit displayed above: @@ -216,8 +209,7 @@ class TestPatelMarkovHayes(QiskitTestCase): """Test the Patel-Markov-Hayes algorithm for synthesizing linear CNOT-only circuits.""" - @ddt.data(cnot_synth, synth_cnot_count_full_pmh) - def test_patel_markov_hayes(self, synth_func): + def test_patel_markov_hayes(self): """Test synthesis of a small linear circuit (example from paper, Figure 3). @@ -252,7 +244,7 @@ def test_patel_markov_hayes(self, synth_func): [1, 1, 0, 1, 1, 1], [0, 0, 1, 1, 1, 0], ] - c_patel = synth_func(state) + c_patel = synth_cnot_count_full_pmh(state) unitary_patel = UnitaryGate(Operator(c_patel)) # Create the circuit displayed above: From 06067a4b9ba3a99e48bc6c582aa272e41ff91cca Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 30 Mar 2023 08:20:01 +0000 Subject: [PATCH 08/15] style fix --- qiskit/transpiler/synthesis/graysynth.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/synthesis/graysynth.py b/qiskit/transpiler/synthesis/graysynth.py index 2e6c85e1c068..7ab396b96b01 100644 --- a/qiskit/transpiler/synthesis/graysynth.py +++ b/qiskit/transpiler/synthesis/graysynth.py @@ -25,8 +25,10 @@ from qiskit.synthesis.linear_phase.cnot_phase_synth import * from qiskit.utils.deprecation import deprecate_func + @deprecate_func( - additional_msg="Instead, use the function qiskit.synthesis.synth_cnot_count_full_pmh.", since="0.24.0" + additional_msg="Instead, use the function qiskit.synthesis.synth_cnot_count_full_pmh.", + since="0.24.0", ) def cnot_synth(state, section_size=2): """ @@ -57,8 +59,10 @@ def cnot_synth(state, section_size=2): """ return synth_cnot_count_full_pmh(state, section_size=section_size) + @deprecate_func( - additional_msg="Instead, use the function qiskit.synthesis.synth_cnot_phase_aam.", since="0.24.0" + additional_msg="Instead, use the function qiskit.synthesis.synth_cnot_phase_aam.", + since="0.24.0", ) def graysynth(cnots, angles, section_size=2): """Synthesize a cnot-phase circuit.""" From 7d6fdfd9cab8003e44ce8bf9b10163e89b0a4114 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 30 Mar 2023 09:36:44 +0000 Subject: [PATCH 09/15] add release notes --- .../notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 releasenotes/notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml diff --git a/releasenotes/notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml b/releasenotes/notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml new file mode 100644 index 000000000000..43f6cc4d7720 --- /dev/null +++ b/releasenotes/notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml @@ -0,0 +1,9 @@ +--- +deprecations: + - | + The functions :func:`qiskit.transpiler.synthesis.cnot_synth` + and :func:`qiskit.transpiler.synthesis.graysynth` + will be deprecated in the future release and subsequently removed after that. + They are replaced by the two functions + :func:`qiskit.synthesis.synth_cnot_count_full_pmh` and + :func:`qiskit.synthesis.qiskit.synthesis.synth_cnot_phase_aam` respectively. From 0ff730ed731da09d333a701fa17ef79417792982 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Tue, 4 Apr 2023 06:13:01 +0000 Subject: [PATCH 10/15] remove deprecation functions from graysynth.py --- qiskit/transpiler/synthesis/graysynth.py | 65 ++++++++++++++++++++---- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/qiskit/transpiler/synthesis/graysynth.py b/qiskit/transpiler/synthesis/graysynth.py index 7ab396b96b01..b55bd27c0f0f 100644 --- a/qiskit/transpiler/synthesis/graysynth.py +++ b/qiskit/transpiler/synthesis/graysynth.py @@ -23,13 +23,8 @@ # TODO: Deprecate in 0.24.0 and remove in 0.26.0 from qiskit.synthesis.linear.cnot_synth import * from qiskit.synthesis.linear_phase.cnot_phase_synth import * -from qiskit.utils.deprecation import deprecate_func -@deprecate_func( - additional_msg="Instead, use the function qiskit.synthesis.synth_cnot_count_full_pmh.", - since="0.24.0", -) def cnot_synth(state, section_size=2): """ Synthesize linear reversible circuits for all-to-all architecture @@ -60,10 +55,60 @@ def cnot_synth(state, section_size=2): return synth_cnot_count_full_pmh(state, section_size=section_size) -@deprecate_func( - additional_msg="Instead, use the function qiskit.synthesis.synth_cnot_phase_aam.", - since="0.24.0", -) def graysynth(cnots, angles, section_size=2): - """Synthesize a cnot-phase circuit.""" + """This function is an implementation of the GraySynth algorithm of + Amy, Azimadeh and Mosca. + + GraySynth is a heuristic algorithm from [1] for synthesizing small parity networks. + It is inspired by Gray codes. Given a set of binary strings S + (called "cnots" bellow), the algorithm synthesizes a parity network for S by + repeatedly choosing an index i to expand and then effectively recursing on + the co-factors S_0 and S_1, consisting of the strings y in S, + with y_i = 0 or 1 respectively. As a subset S is recursively expanded, + CNOT gates are applied so that a designated target bit contains the + (partial) parity ksi_y(x) where y_i = 1 if and only if y'_i = 1 for all + y' in S. If S is a singleton {y'}, then y = y', hence the target bit contains + the value ksi_y'(x) as desired. + + Notably, rather than uncomputing this sequence of CNOT gates when a subset S + is finished being synthesized, the algorithm maintains the invariant + that the remaining parities to be computed are expressed over the current state + of bits. This allows the algorithm to avoid the 'backtracking' inherent in + uncomputing-based methods. + + The algorithm is described in detail in section 4 of [1]. + + Args: + cnots (list[list]): a matrix whose columns are the parities to be synthesized + e.g.:: + + [[0, 1, 1, 1, 1, 1], + [1, 0, 0, 1, 1, 1], + [1, 0, 0, 1, 0, 0], + [0, 0, 1, 0, 1, 0]] + + corresponds to:: + + x1^x2 + x0 + x0^x3 + x0^x1^x2 + x0^x1^x3 + x0^x1 + + angles (list): a list containing all the phase-shift gates which are + to be applied, in the same order as in "cnots". A number is + interpreted as the angle of p(angle), otherwise the elements + have to be 't', 'tdg', 's', 'sdg' or 'z'. + + section_size (int): the size of every section, used in _lwr_cnot_synth(), in the + Patel–Markov–Hayes algorithm. section_size must be a factor of num_qubits. + + Returns: + QuantumCircuit: the decomposed quantum circuit. + + Raises: + QiskitError: when dimensions of cnots and angles don't align. + + References: + 1. Amy, Matthew, Parsiad Azimzadeh, and Michele Mosca. + *On the controlled-NOT complexity of controlled-NOT–phase circuits.*, + Quantum Science and Technology 4.1 (2018): 015002. + `arXiv:1712.01859 `_ + """ return synth_cnot_phase_aam(cnots, angles, section_size=section_size) From 69182f3386612907c315bad14a5762a7997b56e9 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Tue, 4 Apr 2023 06:13:27 +0000 Subject: [PATCH 11/15] update tests --- .../synthesis/test_cnot_phase_synthesis.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/python/synthesis/test_cnot_phase_synthesis.py b/test/python/synthesis/test_cnot_phase_synthesis.py index ee257c543023..725ad7f84967 100644 --- a/test/python/synthesis/test_cnot_phase_synthesis.py +++ b/test/python/synthesis/test_cnot_phase_synthesis.py @@ -21,6 +21,10 @@ from qiskit.extensions.unitary import UnitaryGate from qiskit.synthesis.linear import synth_cnot_count_full_pmh from qiskit.synthesis.linear_phase import synth_cnot_phase_aam +from qiskit.transpiler.synthesis.graysynth import ( + cnot_synth, + graysynth, +) from qiskit.test import QiskitTestCase @@ -28,7 +32,8 @@ class TestGraySynth(QiskitTestCase): """Test the Gray-Synth algorithm.""" - def test_gray_synth(self): + @ddt.data(synth_cnot_phase_aam, graysynth) + def test_gray_synth(self, synth_func): """Test synthesis of a small parity network via gray_synth. The algorithm should take the following matrix as an input: @@ -70,7 +75,7 @@ def test_gray_synth(self): [0, 1, 0, 0, 1, 0], ] angles = ["s", "t", "z", "s", "t", "t"] - c_gray = synth_cnot_phase_aam(cnots, angles) + c_gray = synth_func(cnots, angles) unitary_gray = UnitaryGate(Operator(c_gray)) # Create the circuit displayed above: @@ -99,7 +104,8 @@ def test_gray_synth(self): # Check if the two circuits are equivalent self.assertEqual(unitary_gray, unitary_compare) - def test_paper_example(self): + @ddt.data(synth_cnot_phase_aam, graysynth) + def test_paper_example(self, synth_func): """Test synthesis of a diagonal operator from the paper. The diagonal operator in Example 4.2 @@ -127,7 +133,7 @@ def test_paper_example(self): """ cnots = [[0, 1, 1, 1, 1, 1], [1, 0, 0, 1, 1, 1], [1, 0, 0, 1, 0, 0], [0, 0, 1, 0, 1, 0]] angles = ["t"] * 6 - c_gray = synth_cnot_phase_aam(cnots, angles) + c_gray = synth_func(cnots, angles) unitary_gray = UnitaryGate(Operator(c_gray)) # Create the circuit displayed above: @@ -153,7 +159,8 @@ def test_paper_example(self): # Check if the two circuits are equivalent self.assertEqual(unitary_gray, unitary_compare) - def test_ccz(self): + @ddt.data(synth_cnot_phase_aam, graysynth) + def test_ccz(self, synth_func): """Test synthesis of the doubly-controlled Z gate. The diagonal operator in Example 4.3 @@ -179,7 +186,7 @@ def test_ccz(self): """ cnots = [[1, 0, 0, 1, 1, 0, 1], [0, 1, 0, 1, 0, 1, 1], [0, 0, 1, 0, 1, 1, 1]] angles = ["t", "t", "t", "tdg", "tdg", "tdg", "t"] - c_gray = synth_cnot_phase_aam(cnots, angles) + c_gray = synth_func(cnots, angles) unitary_gray = UnitaryGate(Operator(c_gray)) # Create the circuit displayed above: @@ -209,7 +216,8 @@ class TestPatelMarkovHayes(QiskitTestCase): """Test the Patel-Markov-Hayes algorithm for synthesizing linear CNOT-only circuits.""" - def test_patel_markov_hayes(self): + @ddt.data(synth_cnot_count_full_pmh, cnot_synth) + def test_patel_markov_hayes(self, synth_func): """Test synthesis of a small linear circuit (example from paper, Figure 3). @@ -244,7 +252,7 @@ def test_patel_markov_hayes(self): [1, 1, 0, 1, 1, 1], [0, 0, 1, 1, 1, 0], ] - c_patel = synth_cnot_count_full_pmh(state) + c_patel = synth_func(state) unitary_patel = UnitaryGate(Operator(c_patel)) # Create the circuit displayed above: From 17916330016e1ed4c8bb4d970a57d44e5cf47c23 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Tue, 4 Apr 2023 06:28:55 +0000 Subject: [PATCH 12/15] update release notes as a bug fix --- qiskit/synthesis/linear_phase/cnot_phase_synth.py | 2 +- qiskit/transpiler/synthesis/graysynth.py | 2 +- .../notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml | 9 --------- .../notes/rename-graysynth-3fa4fcb7d096ab35.yaml | 10 ++++++++++ 4 files changed, 12 insertions(+), 11 deletions(-) delete mode 100644 releasenotes/notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml create mode 100644 releasenotes/notes/rename-graysynth-3fa4fcb7d096ab35.yaml diff --git a/qiskit/synthesis/linear_phase/cnot_phase_synth.py b/qiskit/synthesis/linear_phase/cnot_phase_synth.py index 21a4a0b5de37..f1b49cc8d8df 100644 --- a/qiskit/synthesis/linear_phase/cnot_phase_synth.py +++ b/qiskit/synthesis/linear_phase/cnot_phase_synth.py @@ -74,7 +74,7 @@ def synth_cnot_phase_aam(cnots, angles, section_size=2): QiskitError: when dimensions of cnots and angles don't align. References: - 1. Amy, Matthew, Parsiad Azimzadeh, and Michele Mosca. + 1. Matthew Amy, Parsiad Azimzadeh, and Michele Mosca. *On the controlled-NOT complexity of controlled-NOT–phase circuits.*, Quantum Science and Technology 4.1 (2018): 015002. `arXiv:1712.01859 `_ diff --git a/qiskit/transpiler/synthesis/graysynth.py b/qiskit/transpiler/synthesis/graysynth.py index b55bd27c0f0f..7f0ab1c08ba0 100644 --- a/qiskit/transpiler/synthesis/graysynth.py +++ b/qiskit/transpiler/synthesis/graysynth.py @@ -106,7 +106,7 @@ def graysynth(cnots, angles, section_size=2): QiskitError: when dimensions of cnots and angles don't align. References: - 1. Amy, Matthew, Parsiad Azimzadeh, and Michele Mosca. + 1. Matthew Amy, Parsiad Azimzadeh, and Michele Mosca. *On the controlled-NOT complexity of controlled-NOT–phase circuits.*, Quantum Science and Technology 4.1 (2018): 015002. `arXiv:1712.01859 `_ diff --git a/releasenotes/notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml b/releasenotes/notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml deleted file mode 100644 index 43f6cc4d7720..000000000000 --- a/releasenotes/notes/deprecate-graysynth-3fa4fcb7d096ab35.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -deprecations: - - | - The functions :func:`qiskit.transpiler.synthesis.cnot_synth` - and :func:`qiskit.transpiler.synthesis.graysynth` - will be deprecated in the future release and subsequently removed after that. - They are replaced by the two functions - :func:`qiskit.synthesis.synth_cnot_count_full_pmh` and - :func:`qiskit.synthesis.qiskit.synthesis.synth_cnot_phase_aam` respectively. diff --git a/releasenotes/notes/rename-graysynth-3fa4fcb7d096ab35.yaml b/releasenotes/notes/rename-graysynth-3fa4fcb7d096ab35.yaml new file mode 100644 index 000000000000..43bce22196d4 --- /dev/null +++ b/releasenotes/notes/rename-graysynth-3fa4fcb7d096ab35.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Added a new function :func:`qiskit.synthesis.qiskit.synthesis.synth_cnot_phase_aam` + which is used to synthesize cnot phase circuits for all-to-all architectures + using the Amy, Azimzadeh, and Mosca method. This function is identical to + the available ``qiskit.transpiler.synthesis.graysynth()`` + function but has a more descriptive name and is more logically placed + in the package tree. This new function supersedes the legacy function + which will likely be deprecated in a future release. From 4304116e852bdb207a319b75aa2116603ac899ce Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 16 Apr 2023 07:06:02 +0000 Subject: [PATCH 13/15] add back graysynth to synthesis/linear --- qiskit/synthesis/linear/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index debc9474caa9..c061ba646ded 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -19,3 +19,7 @@ calc_inverse_matrix, check_invertible_binary_matrix, ) + +# pylint: disable=cyclic-import +# pylint: disable=wrong-import-order +from qiskit.synthesis.linear_phase import synth_cnot_phase_aam as graysynth From e3844345d805a79dbb9304a4cf860dda6efa3fc5 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 16 Apr 2023 07:07:02 +0000 Subject: [PATCH 14/15] fix link in releaese notes --- releasenotes/notes/rename-graysynth-3fa4fcb7d096ab35.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/rename-graysynth-3fa4fcb7d096ab35.yaml b/releasenotes/notes/rename-graysynth-3fa4fcb7d096ab35.yaml index 43bce22196d4..dce2d2711c3d 100644 --- a/releasenotes/notes/rename-graysynth-3fa4fcb7d096ab35.yaml +++ b/releasenotes/notes/rename-graysynth-3fa4fcb7d096ab35.yaml @@ -1,7 +1,7 @@ --- fixes: - | - Added a new function :func:`qiskit.synthesis.qiskit.synthesis.synth_cnot_phase_aam` + Added a new function :func:`qiskit.synthesis.synth_cnot_phase_aam` which is used to synthesize cnot phase circuits for all-to-all architectures using the Amy, Azimzadeh, and Mosca method. This function is identical to the available ``qiskit.transpiler.synthesis.graysynth()`` From 95cfeb50e9ba8267b5cd09a653467613e5505c5a Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 17 Apr 2023 15:43:01 +0100 Subject: [PATCH 15/15] Add note on re-export --- qiskit/synthesis/linear/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index c061ba646ded..115fc557bfa4 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -20,6 +20,6 @@ check_invertible_binary_matrix, ) -# pylint: disable=cyclic-import -# pylint: disable=wrong-import-order +# This is re-import is kept for compatibility with Terra 0.23. Eligible for deprecation in 0.25+. +# pylint: disable=cyclic-import,wrong-import-order from qiskit.synthesis.linear_phase import synth_cnot_phase_aam as graysynth