From d12409814f67044f974dd56e3800e592e1bea20a Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 25 Jan 2023 13:39:27 +0000 Subject: [PATCH 01/23] add cz synthesis for LNN --- qiskit/synthesis/linear/__init__.py | 1 + qiskit/synthesis/linear/cz_depth_lnn.py | 163 ++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 qiskit/synthesis/linear/cz_depth_lnn.py diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index e81f0bdd9c12..a5940d85a2b2 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -14,6 +14,7 @@ from .graysynth import graysynth, synth_cnot_count_full_pmh +from .cz_depth_lnn import synth_cz_depth_line_mr 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/cz_depth_lnn.py b/qiskit/synthesis/linear/cz_depth_lnn.py new file mode 100644 index 000000000000..da5a0f01040f --- /dev/null +++ b/qiskit/synthesis/linear/cz_depth_lnn.py @@ -0,0 +1,163 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023 +# +# 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. + +""" +Optimize the synthesis of an n-qubit circuit contains only CZ gates for +linear nearest neighbor (LNN) connectivity, using CX and phase (S, Sdg or Z) gates. +The 2-qubit depth of the circuit is bounded by 2*n+2. + +References: + [1]: Dmitri Maslov, Martin Roetteler, + Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations, + `arXiv:1705.09176 `_. +""" + +from qiskit.circuit import QuantumCircuit + + +def _append_cx_stage1(qc, n): + for i in range(int(n / 2)): + qc.cx(2 * i, 2 * i + 1) + for i in range(int((n + 1) / 2) - 1): + qc.cx(2 * i + 2, 2 * i + 1) + return qc + + +def _append_cx_stage2(qc, n): + for i in range(int(n / 2)): + qc.cx(2 * i + 1, 2 * i) + for i in range(int((n + 1) / 2) - 1): + qc.cx(2 * i + 1, 2 * i + 2) + return qc + + +def _append_cx_stage(qc, n): + """Append a depth 2 layer of CX gates""" + qc.compose(_append_cx_stage1(qc, n)) + qc.compose(_append_cx_stage2(qc, n)) + return qc + + +def _odd_pattern1(n): + pat = [] + pat.append(n - 2) + for i in range(int((n - 3) / 2)): + pat.append(n - 2 * i - 4) + pat.append(n - 2 * i - 4) + for i in range(int((n - 1) / 2)): + pat.append(2 * i) + pat.append(2 * i) + return pat + + +def _odd_pattern2(n): + pat = [] + for i in range(int((n - 1) / 2)): + pat.append(2 * i + 2) + pat.append(2 * i + 2) + for i in range(int((n - 3) / 2)): + pat.append(n - 2 * i - 2) + pat.append(n - 2 * i - 2) + pat.append(1) + return pat + + +def _even_pattern1(n): + pat = [] + pat.append(n - 1) + for i in range(int((n - 2) / 2)): + pat.append(n - 2 * i - 3) + pat.append(n - 2 * i - 3) + for i in range(int((n - 2) / 2)): + pat.append(2 * i) + pat.append(2 * i) + pat.append(n - 2) + return pat + + +def _even_pattern2(n): + pat = [] + for i in range(int((n - 2) / 2)): + pat.append(2 * (i + 1)) + pat.append(2 * (i + 1)) + for i in range(int(n / 2)): + pat.append(n - 2 * i - 1) + pat.append(n - 2 * i - 1) + return pat + + +def _create_patterns(n): + """Creating the patterns for the phase layer.""" + if (n % 2) == 0: + pat1 = _even_pattern1(n) + pat2 = _even_pattern2(n) + else: + pat1 = _odd_pattern1(n) + pat2 = _odd_pattern2(n) + pats = {} + + layer = 0 + for i in range(n): + pats[(0, i)] = (i, i) + + if (n % 2) == 0: + ind1 = int((2 * n - 4) / 2) + else: + ind1 = int((2 * n - 4) / 2 - 1) + ind2 = 0 + while layer < int(n / 2): + for i in range(n): + pats[(layer + 1, i)] = (pat1[ind1 + i], pat2[ind2 + i]) + layer += 1 + ind1 -= 2 + ind2 += 2 + return pats + + +def synth_cz_depth_line_mr(mat): + """Synthesis of a CZ circuit""" + # A CZ circuit is represented by an upper-diagonal matrix mat: + # mat[i][j]=1 for i Date: Wed, 25 Jan 2023 13:41:02 +0000 Subject: [PATCH 02/23] basic cz synthesis test --- test/python/synthesis/test_cz_synthesis.py | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/python/synthesis/test_cz_synthesis.py diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py new file mode 100644 index 000000000000..e31701a9cc87 --- /dev/null +++ b/test/python/synthesis/test_cz_synthesis.py @@ -0,0 +1,52 @@ +# 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 + +import random +import numpy as np +from ddt import ddt, data +from qiskit import QuantumCircuit +from qiskit.circuit.library import Permutation +from qiskit.synthesis.linear import synth_cz_depth_line_mr +from qiskit.quantum_info import Clifford +from qiskit.test import QiskitTestCase + + +@ddt +class TestCZSynth(QiskitTestCase): + """Test the linear reversible circuit synthesis functions.""" + + @data(5, 6) + def test_lnn(self, n): + """Test the synthesis code.""" + mat = np.zeros((n, n)) + qctest = QuantumCircuit(n) + for _ in range(10): + i = random.randint(0, n - 1) + j = random.randint(0, n - 1) + if i != j: + qctest.cz(i, j) + if j > i: + mat[i][j] = (mat[i][j] + 1) % 2 + else: + mat[j][i] = (mat[j][i] + 1) % 2 + qc = synth_cz_depth_line_mr(mat) + perm = Permutation(num_qubits=n, pattern=range(n)[::-1]) + qctest = qctest.compose(perm) + self.assertEqual(Clifford(qc), Clifford(qctest)) + + +if __name__ == "__main__": + unittest.main() From 3cc7ec06a0da5b438ee4ddff794c0cace7030c1f Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 25 Jan 2023 13:42:08 +0000 Subject: [PATCH 03/23] adjust clifford_decompose_layers to cz synthesis for LNN --- .../clifford/clifford_decompose_layers.py | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 353cfd340535..42c786548909 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -15,9 +15,10 @@ # pylint: disable=invalid-name import numpy as np + from qiskit.circuit import QuantumCircuit from qiskit.exceptions import QiskitError -from qiskit.synthesis.linear import synth_cnot_count_full_pmh +from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cz_depth_line_mr from qiskit.synthesis.linear.linear_matrix_utils import ( calc_inverse_matrix, check_invertible_binary_matrix, @@ -32,24 +33,22 @@ ) -def _default_cx_synth_func(mat, validate): +def _default_cx_synth_func(mat): """ Construct the layer of CX gates from a boolean invertible matrix mat. """ - if validate: - if not check_invertible_binary_matrix(mat): - raise QiskitError("The matrix for CX circuit is not invertible.") + if not check_invertible_binary_matrix(mat): + raise QiskitError("The matrix for CX circuit is not invertible.") CX_circ = synth_cnot_count_full_pmh(mat) CX_circ.name = "CX" - if validate: - _check_gates(CX_circ, ("cx", "swap")) + _check_gates(CX_circ, ("cx", "swap")) return CX_circ -def _default_cz_synth_func(symmetric_mat, validate): +def _default_cz_synth_func(symmetric_mat): """ Construct the layer of CZ gates from a symmetric matrix. """ @@ -61,8 +60,7 @@ def _default_cz_synth_func(symmetric_mat, validate): if symmetric_mat[i][j]: qc.cz(i, j) - if validate: - _check_gates(qc, "cz") + _check_gates(qc, "cz") return qc @@ -108,11 +106,15 @@ def synth_clifford_layers( """ num_qubits = cliff.num_qubits + if cz_synth_func == synth_cz_depth_line_mr: + cliff0 = _reverse_clifford(cliff) + else: + cliff0 = cliff qubit_list = list(range(num_qubits)) layeredCircuit = QuantumCircuit(num_qubits) - H1_circ, cliff1 = _create_graph_state(cliff, validate=validate) + H1_circ, cliff1 = _create_graph_state(cliff0, validate=validate) H2_circ, CZ1_circ, S1_circ, cliff2 = _decompose_graph_state( cliff1, validate=validate, cz_synth_func=cz_synth_func @@ -269,7 +271,7 @@ def _decompose_graph_state(cliff, validate, cz_synth_func): "The multiplication of stabx_inv and stab_z is not a symmetric matrix." ) - CZ1_circ = cz_synth_func(stabz_update, validate=validate) + CZ1_circ = cz_synth_func(stabz_update) for j in range(num_qubits): for i in range(0, j): @@ -339,10 +341,12 @@ def _decompose_hadamard_free(cliff, validate, cz_synth_func, cx_synth_func, cx_c ) return S2_circ, CZ2_circ, CX_circ - CZ2_circ = cz_synth_func(destabz_update, validate=validate) + CZ2_circ = cz_synth_func(destabz_update) mat = destabx.transpose() - CX_circ = cx_synth_func(mat, validate=validate) + if cz_synth_func == synth_cz_depth_line_mr: + mat = np.flip(mat, axis=0) + CX_circ = cx_synth_func(mat) return S2_circ, CZ2_circ, CX_circ From d054b20d794b06339b91585ec48190f3080113f7 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 25 Jan 2023 13:42:57 +0000 Subject: [PATCH 04/23] add a basic test for layered clifford synthesis for LNN --- .../synthesis/test_clifford_decompose_layers.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index e6daa73991f0..9d2131e5ebad 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -22,6 +22,7 @@ from qiskit.quantum_info.operators import Clifford from qiskit.quantum_info import random_clifford from qiskit.synthesis.clifford import synth_clifford_layers +from qiskit.synthesis.linear import synth_cz_depth_line_mr, synth_cnot_depth_line_kms @ddt @@ -49,6 +50,22 @@ def test_decompose_clifford(self, num_qubits): self.assertEqual(circ.data[6].operation.name, "H1") self.assertEqual(circ.data[7].operation.name, "Pauli") + @combine(num_qubits=[5]) + def test_decompose_lnn_depth(self, num_qubits): + """Test layered decomposition for linear-nearest-neighbour connectivity.""" + rng = np.random.default_rng(1234) + samples = 1 + for _ in range(samples): + cliff = random_clifford(num_qubits, seed=rng) + circ = synth_clifford_layers( + cliff, + cx_synth_func=synth_cnot_depth_line_kms, + cz_synth_func=synth_cz_depth_line_mr, + validate=True, + ) + cliff_target = Clifford(circ) + self.assertEqual(cliff, cliff_target) + if __name__ == "__main__": unittest.main() From 4811701fc2a98eb21504755149b416978ae0562f Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 25 Jan 2023 14:56:17 +0000 Subject: [PATCH 05/23] enhance the tests --- .../clifford/clifford_decompose_layers.py | 2 ++ .../test_clifford_decompose_layers.py | 4 ++-- test/python/synthesis/test_cz_synthesis.py | 23 +++++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 42c786548909..9e87624cd9ec 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -138,6 +138,8 @@ def synth_clifford_layers( layeredCircuit.append(S1_circ, qubit_list) layeredCircuit.append(CZ1_circ, qubit_list) + if cz_synth_func == synth_cz_depth_line_mr: + H1_circ = H1_circ.reverse_bits() layeredCircuit.append(H1_circ, qubit_list) # Add Pauli layer to fix the Clifford phase signs diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index 9d2131e5ebad..e77911315fd7 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -50,11 +50,11 @@ def test_decompose_clifford(self, num_qubits): self.assertEqual(circ.data[6].operation.name, "H1") self.assertEqual(circ.data[7].operation.name, "Pauli") - @combine(num_qubits=[5]) + @combine(num_qubits=[4, 5, 6, 7]) def test_decompose_lnn_depth(self, num_qubits): """Test layered decomposition for linear-nearest-neighbour connectivity.""" rng = np.random.default_rng(1234) - samples = 1 + samples = 10 for _ in range(samples): cliff = random_clifford(num_qubits, seed=rng) circ = synth_clifford_layers( diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index e31701a9cc87..cc40a248099c 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -16,7 +16,8 @@ import random import numpy as np -from ddt import ddt, data +from test import combine +from ddt import ddt from qiskit import QuantumCircuit from qiskit.circuit.library import Permutation from qiskit.synthesis.linear import synth_cz_depth_line_mr @@ -28,14 +29,16 @@ class TestCZSynth(QiskitTestCase): """Test the linear reversible circuit synthesis functions.""" - @data(5, 6) - def test_lnn(self, n): - """Test the synthesis code.""" - mat = np.zeros((n, n)) - qctest = QuantumCircuit(n) - for _ in range(10): - i = random.randint(0, n - 1) - j = random.randint(0, n - 1) + @combine(num_qubits=[4, 5, 6, 7]) + def test_cz_synth_lnn(self, num_qubits): + """Test the CZ synthesis code.""" + mat = np.zeros((num_qubits, num_qubits)) + qctest = QuantumCircuit(num_qubits) + samples = 10 + for _ in range(samples): + # Generate a random CZ circuit + i = random.randint(0, num_qubits - 1) + j = random.randint(0, num_qubits - 1) if i != j: qctest.cz(i, j) if j > i: @@ -43,7 +46,7 @@ def test_lnn(self, n): else: mat[j][i] = (mat[j][i] + 1) % 2 qc = synth_cz_depth_line_mr(mat) - perm = Permutation(num_qubits=n, pattern=range(n)[::-1]) + perm = Permutation(num_qubits=num_qubits, pattern=range(num_qubits)[::-1]) qctest = qctest.compose(perm) self.assertEqual(Clifford(qc), Clifford(qctest)) From 2e800428b72842dd50251ee53fe8cb8086128428 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 25 Jan 2023 16:34:28 +0000 Subject: [PATCH 06/23] fix lint, check function name --- qiskit/synthesis/clifford/clifford_decompose_layers.py | 6 +++--- test/python/synthesis/test_cz_synthesis.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 9e87624cd9ec..a4f222104518 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -106,7 +106,7 @@ def synth_clifford_layers( """ num_qubits = cliff.num_qubits - if cz_synth_func == synth_cz_depth_line_mr: + if cz_synth_func.__name__ == synth_cz_depth_line_mr.__name__: cliff0 = _reverse_clifford(cliff) else: cliff0 = cliff @@ -138,7 +138,7 @@ def synth_clifford_layers( layeredCircuit.append(S1_circ, qubit_list) layeredCircuit.append(CZ1_circ, qubit_list) - if cz_synth_func == synth_cz_depth_line_mr: + if cz_synth_func.__name__ == synth_cz_depth_line_mr.__name__: H1_circ = H1_circ.reverse_bits() layeredCircuit.append(H1_circ, qubit_list) @@ -346,7 +346,7 @@ def _decompose_hadamard_free(cliff, validate, cz_synth_func, cx_synth_func, cx_c CZ2_circ = cz_synth_func(destabz_update) mat = destabx.transpose() - if cz_synth_func == synth_cz_depth_line_mr: + if cz_synth_func.__name__ == synth_cz_depth_line_mr.__name__: mat = np.flip(mat, axis=0) CX_circ = cx_synth_func(mat) diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index cc40a248099c..90130f24a654 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -15,8 +15,8 @@ import unittest import random -import numpy as np from test import combine +import numpy as np from ddt import ddt from qiskit import QuantumCircuit from qiskit.circuit.library import Permutation From 83b0ba31aaf7ef63507255687502d33723140601 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 29 Jan 2023 08:18:07 +0000 Subject: [PATCH 07/23] Add utils needed for LNN tests --- .../synthesis/linear/linear_circuits_utils.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index 375c91e9e35f..63c59c4157d2 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -100,3 +100,51 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b best_qc = qc return best_qc + + +def get_circ_cx_depth(qc: QuantumCircuit) -> int: + """Calculate the circuit depth only for CX gates, removing single qubit gates. + + Args: + qc: a QuantumCircuit containing only CX and single qubit gates. + + Returns: + int: the circuit depth only for CX gates. + + Raises: + CircuitError: if qc has a non-CX two-qubit gate. + """ + num_qubits = qc.num_qubits + qc2 = QuantumCircuit(num_qubits) + for instruction in reversed(qc.data): + if instruction.operation.num_qubits > 1: + if instruction.operation.name == "cx": + qc2._append(instruction) + else: + raise CircuitError("The circuit has two-qubits gates different than CX.") + return qc2.depth() + + +def check_lnn_connectivity(qc: QuantumCircuit) -> bool: + """Check that the synthesized circuit qc fits linear nearest neighbor connectivity. + + Args: + qc: a QuantumCircuit containing only CX and single qubit gates. + + Returns: + bool: True if the circuit has linear nearest neighbor connectivity. + + Raises: + CircuitError: if qc has a non-CX two-qubit gate. + """ + for instruction in qc.data: + if instruction.operation.num_qubits > 1: + if instruction.operation.name == "cx": + q0 = qc.find_bit(instruction.qubits[0]).index + q1 = qc.find_bit(instruction.qubits[1]).index + dist = abs(q0 - q1) + if dist != 1: + return False + else: + raise CircuitError("The circuit has two-qubits gates different than CX.") + return True From 065f63bbeb606a10984b45eb157fccac94e07750 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 29 Jan 2023 08:18:46 +0000 Subject: [PATCH 08/23] add tests for LNN and depth --- .../test_clifford_decompose_layers.py | 7 ++- test/python/synthesis/test_cz_synthesis.py | 48 +++++++++++-------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index e77911315fd7..6fcb3dccdab1 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -23,6 +23,7 @@ from qiskit.quantum_info import random_clifford from qiskit.synthesis.clifford import synth_clifford_layers from qiskit.synthesis.linear import synth_cz_depth_line_mr, synth_cnot_depth_line_kms +from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity, get_circ_cx_depth @ddt @@ -52,7 +53,7 @@ def test_decompose_clifford(self, num_qubits): @combine(num_qubits=[4, 5, 6, 7]) def test_decompose_lnn_depth(self, num_qubits): - """Test layered decomposition for linear-nearest-neighbour connectivity.""" + """Test layered decomposition for linear-nearest-neighbour (LNN) connectivity.""" rng = np.random.default_rng(1234) samples = 10 for _ in range(samples): @@ -63,6 +64,10 @@ def test_decompose_lnn_depth(self, num_qubits): cz_synth_func=synth_cz_depth_line_mr, validate=True, ) + # Check that the Clifford circuit 2-qubit depth is bounded by 9*n+4 + self.assertTrue(get_circ_cx_depth(circ.decompose()) <= 9 * num_qubits + 4) + # Check that the Clifford circuit has linear nearest neighbour connectivity + self.assertTrue(check_lnn_connectivity(circ.decompose())) cliff_target = Clifford(circ) self.assertEqual(cliff, cliff_target) diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index 90130f24a654..4a3a038948e4 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -13,14 +13,13 @@ """Test linear reversible circuits synthesis functions.""" import unittest - -import random from test import combine import numpy as np from ddt import ddt from qiskit import QuantumCircuit from qiskit.circuit.library import Permutation from qiskit.synthesis.linear import synth_cz_depth_line_mr +from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity, get_circ_cx_depth from qiskit.quantum_info import Clifford from qiskit.test import QiskitTestCase @@ -31,24 +30,35 @@ class TestCZSynth(QiskitTestCase): @combine(num_qubits=[4, 5, 6, 7]) def test_cz_synth_lnn(self, num_qubits): - """Test the CZ synthesis code.""" - mat = np.zeros((num_qubits, num_qubits)) - qctest = QuantumCircuit(num_qubits) - samples = 10 - for _ in range(samples): + """Test the CZ synthesis code for linear nearest neighbour connectivity.""" + seed = 1234 + rng = np.random.default_rng(seed) + num_gates = 10 + num_trials = 5 + for _ in range(num_trials): + mat = np.zeros((num_qubits, num_qubits)) + qctest = QuantumCircuit(num_qubits) + # Generate a random CZ circuit - i = random.randint(0, num_qubits - 1) - j = random.randint(0, num_qubits - 1) - if i != j: - qctest.cz(i, j) - if j > i: - mat[i][j] = (mat[i][j] + 1) % 2 - else: - mat[j][i] = (mat[j][i] + 1) % 2 - qc = synth_cz_depth_line_mr(mat) - perm = Permutation(num_qubits=num_qubits, pattern=range(num_qubits)[::-1]) - qctest = qctest.compose(perm) - self.assertEqual(Clifford(qc), Clifford(qctest)) + for _ in range(num_gates): + i = rng.integers(num_qubits - 1) + j = rng.integers(num_qubits - 1) + if i != j: + qctest.cz(i, j) + if j > i: + mat[i][j] = (mat[i][j] + 1) % 2 + else: + mat[j][i] = (mat[j][i] + 1) % 2 + + qc = synth_cz_depth_line_mr(mat) + # Check that the output circuit 2-qubit depth equals to 2*n+2 + self.assertTrue(get_circ_cx_depth(qc) == 2 * num_qubits + 2) + # Check that the output circuit has LNN connectivity + self.assertTrue(check_lnn_connectivity(qc)) + # Assert that we get the same element, up to reverse order of qubits + perm = Permutation(num_qubits=num_qubits, pattern=range(num_qubits)[::-1]) + qctest = qctest.compose(perm) + self.assertEqual(Clifford(qc), Clifford(qctest)) if __name__ == "__main__": From 25e094a362ac59493a2fb003a10ea0caf84ca3ed Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 29 Jan 2023 09:29:43 +0000 Subject: [PATCH 09/23] Add documentation for cz synthesis --- qiskit/synthesis/__init__.py | 11 +++++-- .../clifford/clifford_decompose_layers.py | 2 +- qiskit/synthesis/linear/cz_depth_lnn.py | 32 +++++++++++++++---- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index b843c5a8eb82..296b47456729 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -29,13 +29,14 @@ SuzukiTrotter MatrixExponential -Linear Function Synthesis -========================= +Linear Function and Linear-Phase Synthesis +================================+========= .. autosummary:: :toctree: ../stubs/ synth_cnot_count_full_pmh synth_cnot_depth_line_kms + synth_cz_depth_line_mr Permutation Synthesis ===================== @@ -93,7 +94,11 @@ synth_permutation_basic, synth_permutation_acg, ) -from .linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms +from .linear import ( + synth_cnot_count_full_pmh, + synth_cnot_depth_line_kms, + synth_cz_depth_line_mr, +) from .clifford import ( synth_clifford_full, synth_clifford_ag, diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index a4f222104518..f968508670ea 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -72,7 +72,7 @@ def synth_clifford_layers( validate=False, ): """Synthesis of a Clifford into layers, it provides a similar decomposition to the synthesis - described in Lemma 8 of [1]. + described in Lemma 8 of Bravyi and Maslov. For example, a 5-qubit Clifford circuit is decomposed into the following layers: diff --git a/qiskit/synthesis/linear/cz_depth_lnn.py b/qiskit/synthesis/linear/cz_depth_lnn.py index da5a0f01040f..bceded630e7b 100644 --- a/qiskit/synthesis/linear/cz_depth_lnn.py +++ b/qiskit/synthesis/linear/cz_depth_lnn.py @@ -13,7 +13,7 @@ """ Optimize the synthesis of an n-qubit circuit contains only CZ gates for linear nearest neighbor (LNN) connectivity, using CX and phase (S, Sdg or Z) gates. -The 2-qubit depth of the circuit is bounded by 2*n+2. +The two-qubit depth of the circuit is bounded by 2*n+2. References: [1]: Dmitri Maslov, Martin Roetteler, @@ -21,10 +21,12 @@ `arXiv:1705.09176 `_. """ +import numpy as np from qiskit.circuit import QuantumCircuit def _append_cx_stage1(qc, n): + """A single layer of CX gates.""" for i in range(int(n / 2)): qc.cx(2 * i, 2 * i + 1) for i in range(int((n + 1) / 2) - 1): @@ -33,6 +35,7 @@ def _append_cx_stage1(qc, n): def _append_cx_stage2(qc, n): + """A single layer of CX gates.""" for i in range(int(n / 2)): qc.cx(2 * i + 1, 2 * i) for i in range(int((n + 1) / 2) - 1): @@ -48,6 +51,7 @@ def _append_cx_stage(qc, n): def _odd_pattern1(n): + """A pattern for odd number of qubits.""" pat = [] pat.append(n - 2) for i in range(int((n - 3) / 2)): @@ -60,6 +64,7 @@ def _odd_pattern1(n): def _odd_pattern2(n): + """A pattern for odd number of qubits.""" pat = [] for i in range(int((n - 1) / 2)): pat.append(2 * i + 2) @@ -72,6 +77,7 @@ def _odd_pattern2(n): def _even_pattern1(n): + """A pattern for even number of qubits.""" pat = [] pat.append(n - 1) for i in range(int((n - 2) / 2)): @@ -85,6 +91,7 @@ def _even_pattern1(n): def _even_pattern2(n): + """A pattern for even number of qubits.""" pat = [] for i in range(int((n - 2) / 2)): pat.append(2 * (i + 1)) @@ -123,11 +130,24 @@ def _create_patterns(n): return pats -def synth_cz_depth_line_mr(mat): - """Synthesis of a CZ circuit""" - # A CZ circuit is represented by an upper-diagonal matrix mat: - # mat[i][j]=1 for i`_. + """ num_qubits = mat.shape[0] pats = _create_patterns(num_qubits) patlist = [] From 96884dd95815dfb1f0d3a6dead0907b556091bb1 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 29 Jan 2023 09:53:35 +0000 Subject: [PATCH 10/23] fix misprint --- qiskit/synthesis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 296b47456729..eccfb1ea7f34 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -30,7 +30,7 @@ MatrixExponential Linear Function and Linear-Phase Synthesis -================================+========= +========================================== .. autosummary:: :toctree: ../stubs/ From 66545290c3edf9c89e8731c4e66826be81b00cd7 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 29 Jan 2023 10:43:55 +0000 Subject: [PATCH 11/23] add synth_clifford_depth_lnn --- qiskit/synthesis/__init__.py | 2 ++ qiskit/synthesis/clifford/__init__.py | 2 +- .../clifford/clifford_decompose_layers.py | 34 ++++++++++++++++++- .../test_clifford_decompose_layers.py | 10 ++---- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index eccfb1ea7f34..e507dbd5b748 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -59,6 +59,7 @@ synth_clifford_bm synth_clifford_greedy synth_clifford_layers + synth_clifford_depth_lnn CNOTDihedral Synthesis ====================== @@ -105,6 +106,7 @@ synth_clifford_bm, synth_clifford_greedy, synth_clifford_layers, + synth_clifford_depth_lnn, ) from .cnotdihedral import ( synth_cnotdihedral_full, diff --git a/qiskit/synthesis/clifford/__init__.py b/qiskit/synthesis/clifford/__init__.py index 79452c2a2064..5b170b2c2492 100644 --- a/qiskit/synthesis/clifford/__init__.py +++ b/qiskit/synthesis/clifford/__init__.py @@ -16,4 +16,4 @@ from .clifford_decompose_ag import synth_clifford_ag from .clifford_decompose_bm import synth_clifford_bm, _decompose_clifford_1q from .clifford_decompose_greedy import synth_clifford_greedy -from .clifford_decompose_layers import synth_clifford_layers +from .clifford_decompose_layers import synth_clifford_layers, synth_clifford_depth_lnn diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index f968508670ea..5996a94c4231 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -18,7 +18,11 @@ from qiskit.circuit import QuantumCircuit from qiskit.exceptions import QiskitError -from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cz_depth_line_mr +from qiskit.synthesis.linear import ( + synth_cnot_count_full_pmh, + synth_cnot_depth_line_kms, + synth_cz_depth_line_mr, +) from qiskit.synthesis.linear.linear_matrix_utils import ( calc_inverse_matrix, check_invertible_binary_matrix, @@ -395,3 +399,31 @@ def _check_gates(qc, allowed_gates): for inst, _, _ in qc.data: if not inst.name in allowed_gates: raise QiskitError("The gate name is not in the allowed_gates list.") + + +def synth_clifford_depth_lnn(cliff): + """Synthesis of a Clifford into layers for linear-nearest neighbour connectivity. + + The depth of the synthesized n-qubit circuit is bounded by 9*n+4, which is not optimal. + It should be replaced by a better algorithm that provides a depth bounded by 7*n+2. + + Args: + cliff (Clifford): a clifford operator. + + Return: + QuantumCircuit: a circuit implementation of the Clifford. + + Reference: + 1. S. Bravyi, D. Maslov, *Hadamard-free circuits expose the + structure of the Clifford group*, + `arXiv:2003.09412 [quant-ph] `_ + 2. Dmitri Maslov, Martin Roetteler, + Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations, + `arXiv:1705.09176 `_. + """ + circ = synth_clifford_layers( + cliff, + cx_synth_func=synth_cnot_depth_line_kms, + cz_synth_func=synth_cz_depth_line_mr, + ) + return circ diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index 6fcb3dccdab1..96436ca3fec6 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -21,8 +21,7 @@ from qiskit.test import QiskitTestCase from qiskit.quantum_info.operators import Clifford from qiskit.quantum_info import random_clifford -from qiskit.synthesis.clifford import synth_clifford_layers -from qiskit.synthesis.linear import synth_cz_depth_line_mr, synth_cnot_depth_line_kms +from qiskit.synthesis.clifford import synth_clifford_layers, synth_clifford_depth_lnn from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity, get_circ_cx_depth @@ -58,12 +57,7 @@ def test_decompose_lnn_depth(self, num_qubits): samples = 10 for _ in range(samples): cliff = random_clifford(num_qubits, seed=rng) - circ = synth_clifford_layers( - cliff, - cx_synth_func=synth_cnot_depth_line_kms, - cz_synth_func=synth_cz_depth_line_mr, - validate=True, - ) + circ = synth_clifford_depth_lnn(cliff) # Check that the Clifford circuit 2-qubit depth is bounded by 9*n+4 self.assertTrue(get_circ_cx_depth(circ.decompose()) <= 9 * num_qubits + 4) # Check that the Clifford circuit has linear nearest neighbour connectivity From e6e955c58aea247d15b5746b2cc0c8791dae30de Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Tue, 31 Jan 2023 09:54:05 +0000 Subject: [PATCH 12/23] updates following review --- qiskit/synthesis/__init__.py | 4 +-- qiskit/synthesis/clifford/__init__.py | 2 +- .../clifford/clifford_decompose_layers.py | 36 +++++++------------ qiskit/synthesis/linear/cz_depth_lnn.py | 20 +++++++---- .../test_clifford_decompose_layers.py | 4 +-- test/python/synthesis/test_cz_synthesis.py | 2 +- 6 files changed, 33 insertions(+), 35 deletions(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index e507dbd5b748..3403944cf0be 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -59,7 +59,7 @@ synth_clifford_bm synth_clifford_greedy synth_clifford_layers - synth_clifford_depth_lnn + synth_clifford_layers_lnn CNOTDihedral Synthesis ====================== @@ -106,7 +106,7 @@ synth_clifford_bm, synth_clifford_greedy, synth_clifford_layers, - synth_clifford_depth_lnn, + synth_clifford_layers_lnn, ) from .cnotdihedral import ( synth_cnotdihedral_full, diff --git a/qiskit/synthesis/clifford/__init__.py b/qiskit/synthesis/clifford/__init__.py index 5b170b2c2492..dbb479f07c78 100644 --- a/qiskit/synthesis/clifford/__init__.py +++ b/qiskit/synthesis/clifford/__init__.py @@ -16,4 +16,4 @@ from .clifford_decompose_ag import synth_clifford_ag from .clifford_decompose_bm import synth_clifford_bm, _decompose_clifford_1q from .clifford_decompose_greedy import synth_clifford_greedy -from .clifford_decompose_layers import synth_clifford_layers, synth_clifford_depth_lnn +from .clifford_decompose_layers import synth_clifford_layers, synth_clifford_layers_lnn diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 5996a94c4231..b5407f2241a2 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -25,7 +25,6 @@ ) from qiskit.synthesis.linear.linear_matrix_utils import ( calc_inverse_matrix, - check_invertible_binary_matrix, _compute_rank, _gauss_elimination, _gauss_elimination_with_perm, @@ -41,14 +40,9 @@ def _default_cx_synth_func(mat): """ Construct the layer of CX gates from a boolean invertible matrix mat. """ - if not check_invertible_binary_matrix(mat): - raise QiskitError("The matrix for CX circuit is not invertible.") - CX_circ = synth_cnot_count_full_pmh(mat) CX_circ.name = "CX" - _check_gates(CX_circ, ("cx", "swap")) - return CX_circ @@ -63,8 +57,6 @@ def _default_cz_synth_func(symmetric_mat): for i in range(0, j): if symmetric_mat[i][j]: qc.cz(i, j) - - _check_gates(qc, "cz") return qc @@ -73,6 +65,7 @@ def synth_clifford_layers( cx_synth_func=_default_cx_synth_func, cz_synth_func=_default_cz_synth_func, cx_cz_synth_func=None, + reverse_qubits=False, validate=False, ): """Synthesis of a Clifford into layers, it provides a similar decomposition to the synthesis @@ -99,6 +92,8 @@ def synth_clifford_layers( cz_synth_func (Callable): a function to decompose the CZ sub-circuit. cx_cz_synth_func (Callable): optional, a function to decompose both sub-circuits CZ and CX. validate (Boolean): if True, validates the synthesis process. + reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr, + since this function returns a circuit that inverts the order of qubits. Return: QuantumCircuit: a circuit implementation of the Clifford. @@ -110,7 +105,7 @@ def synth_clifford_layers( """ num_qubits = cliff.num_qubits - if cz_synth_func.__name__ == synth_cz_depth_line_mr.__name__: + if reverse_qubits: cliff0 = _reverse_clifford(cliff) else: cliff0 = cliff @@ -130,6 +125,7 @@ def synth_clifford_layers( cz_synth_func=cz_synth_func, cx_synth_func=cx_synth_func, cx_cz_synth_func=cx_cz_synth_func, + reverse_qubits=reverse_qubits, ) layeredCircuit.append(S2_circ, qubit_list) @@ -142,7 +138,7 @@ def synth_clifford_layers( layeredCircuit.append(S1_circ, qubit_list) layeredCircuit.append(CZ1_circ, qubit_list) - if cz_synth_func.__name__ == synth_cz_depth_line_mr.__name__: + if reverse_qubits: H1_circ = H1_circ.reverse_bits() layeredCircuit.append(H1_circ, qubit_list) @@ -296,7 +292,9 @@ def _decompose_graph_state(cliff, validate, cz_synth_func): return H2_circ, CZ1_circ, S1_circ, cliff_cpy -def _decompose_hadamard_free(cliff, validate, cz_synth_func, cx_synth_func, cx_cz_synth_func): +def _decompose_hadamard_free( + cliff, validate, cz_synth_func, cx_synth_func, cx_cz_synth_func, reverse_qubits +): """Assumes that the Clifford cliff is Hadamard free. Decompose it into the layers S2 - CZ2 - CX, where S2_circ is a circuit that can contain only S gates, @@ -309,6 +307,7 @@ def _decompose_hadamard_free(cliff, validate, cz_synth_func, cx_synth_func, cx_c cz_synth_func (Callable): a function to decompose the CZ sub-circuit. cx_synth_func (Callable): a function to decompose the CX sub-circuit. cx_cz_synth_func (Callable): optional, a function to decompose both sub-circuits CZ and CX. + reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr. Return: S2_circ: a circuit that can contain only S gates. @@ -350,7 +349,7 @@ def _decompose_hadamard_free(cliff, validate, cz_synth_func, cx_synth_func, cx_c CZ2_circ = cz_synth_func(destabz_update) mat = destabx.transpose() - if cz_synth_func.__name__ == synth_cz_depth_line_mr.__name__: + if reverse_qubits: mat = np.flip(mat, axis=0) CX_circ = cx_synth_func(mat) @@ -391,17 +390,7 @@ def _calc_pauli_diff(cliff, cliff_target): return pauli_circ -def _check_gates(qc, allowed_gates): - """Check that quantum circuit qc consists only of allowed_gates. - qc - a QuantumCircuit - allowed_gates - list of strings - """ - for inst, _, _ in qc.data: - if not inst.name in allowed_gates: - raise QiskitError("The gate name is not in the allowed_gates list.") - - -def synth_clifford_depth_lnn(cliff): +def synth_clifford_layers_lnn(cliff): """Synthesis of a Clifford into layers for linear-nearest neighbour connectivity. The depth of the synthesized n-qubit circuit is bounded by 9*n+4, which is not optimal. @@ -425,5 +414,6 @@ def synth_clifford_depth_lnn(cliff): cliff, cx_synth_func=synth_cnot_depth_line_kms, cz_synth_func=synth_cz_depth_line_mr, + reverse_qubits=True, ) return circ diff --git a/qiskit/synthesis/linear/cz_depth_lnn.py b/qiskit/synthesis/linear/cz_depth_lnn.py index bceded630e7b..eb94abc7ac4e 100644 --- a/qiskit/synthesis/linear/cz_depth_lnn.py +++ b/qiskit/synthesis/linear/cz_depth_lnn.py @@ -44,14 +44,16 @@ def _append_cx_stage2(qc, n): def _append_cx_stage(qc, n): - """Append a depth 2 layer of CX gates""" + """Append a depth 2 layer of CX gates.""" qc.compose(_append_cx_stage1(qc, n)) qc.compose(_append_cx_stage2(qc, n)) return qc def _odd_pattern1(n): - """A pattern for odd number of qubits.""" + """A pattern denoted by Pj in [1] for odd number of qubits: + [n-2, n-4, n-4, ..., 3, 3, 1, 1, 0, 0, 2, 2, ..., n-3, n-3] + """ pat = [] pat.append(n - 2) for i in range(int((n - 3) / 2)): @@ -64,7 +66,9 @@ def _odd_pattern1(n): def _odd_pattern2(n): - """A pattern for odd number of qubits.""" + """A pattern denoted by Pk in [1] for odd number of qubits: + [2, 2, 4, 4, ..., n-1, n-1, n-2, n-2, n-4, n-4, ..., 5, 5, 3, 3, 1] + """ pat = [] for i in range(int((n - 1) / 2)): pat.append(2 * i + 2) @@ -77,7 +81,9 @@ def _odd_pattern2(n): def _even_pattern1(n): - """A pattern for even number of qubits.""" + """A pattern denoted by Pj in [1] for even number of qubits: + [n-1, n-3, n-3, n-5, n-5, ..., 1, 1, 0, 0, 2, 2, ..., n-4, n-4, n-2] + """ pat = [] pat.append(n - 1) for i in range(int((n - 2) / 2)): @@ -91,7 +97,9 @@ def _even_pattern1(n): def _even_pattern2(n): - """A pattern for even number of qubits.""" + """A pattern denoted by Pk in [1] for even number of qubits: + [2, 2, 4, 4, ..., n-2, n-2, n-1, n-1, ..., 3, 3, 1, 1] + """ pat = [] for i in range(int((n - 2) / 2)): pat.append(2 * (i + 1)) @@ -103,7 +111,7 @@ def _even_pattern2(n): def _create_patterns(n): - """Creating the patterns for the phase layer.""" + """Creating the patterns for the phase layers.""" if (n % 2) == 0: pat1 = _even_pattern1(n) pat2 = _even_pattern2(n) diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index 96436ca3fec6..e463598087e4 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -21,7 +21,7 @@ from qiskit.test import QiskitTestCase from qiskit.quantum_info.operators import Clifford from qiskit.quantum_info import random_clifford -from qiskit.synthesis.clifford import synth_clifford_layers, synth_clifford_depth_lnn +from qiskit.synthesis.clifford import synth_clifford_layers, synth_clifford_layers_lnn from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity, get_circ_cx_depth @@ -57,7 +57,7 @@ def test_decompose_lnn_depth(self, num_qubits): samples = 10 for _ in range(samples): cliff = random_clifford(num_qubits, seed=rng) - circ = synth_clifford_depth_lnn(cliff) + circ = synth_clifford_layers_lnn(cliff) # Check that the Clifford circuit 2-qubit depth is bounded by 9*n+4 self.assertTrue(get_circ_cx_depth(circ.decompose()) <= 9 * num_qubits + 4) # Check that the Clifford circuit has linear nearest neighbour connectivity diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index 4a3a038948e4..314f2e19c2ea 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -10,7 +10,7 @@ # 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.""" +"""Test CZ circuits synthesis functions.""" import unittest from test import combine From a5570f1d0a9291174134fe8cb168a8e00eca7cb1 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Wed, 8 Feb 2023 15:09:17 +0000 Subject: [PATCH 13/23] rename synth_clifford_depth_lnn --- qiskit/synthesis/__init__.py | 4 ++-- qiskit/synthesis/clifford/__init__.py | 2 +- qiskit/synthesis/clifford/clifford_decompose_layers.py | 8 ++++++-- test/python/synthesis/test_clifford_decompose_layers.py | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 1044e47cd333..7311b9a51126 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -60,7 +60,7 @@ synth_clifford_bm synth_clifford_greedy synth_clifford_layers - synth_clifford_layers_lnn + synth_clifford_depth_lnn CNOTDihedral Synthesis ====================== @@ -107,7 +107,7 @@ synth_clifford_bm, synth_clifford_greedy, synth_clifford_layers, - synth_clifford_layers_lnn, + synth_clifford_depth_lnn, ) from .cnotdihedral import ( synth_cnotdihedral_full, diff --git a/qiskit/synthesis/clifford/__init__.py b/qiskit/synthesis/clifford/__init__.py index dbb479f07c78..5b170b2c2492 100644 --- a/qiskit/synthesis/clifford/__init__.py +++ b/qiskit/synthesis/clifford/__init__.py @@ -16,4 +16,4 @@ from .clifford_decompose_ag import synth_clifford_ag from .clifford_decompose_bm import synth_clifford_bm, _decompose_clifford_1q from .clifford_decompose_greedy import synth_clifford_greedy -from .clifford_decompose_layers import synth_clifford_layers, synth_clifford_layers_lnn +from .clifford_decompose_layers import synth_clifford_layers, synth_clifford_depth_lnn diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index b5407f2241a2..fdd5004dc237 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -390,11 +390,11 @@ def _calc_pauli_diff(cliff, cliff_target): return pauli_circ -def synth_clifford_layers_lnn(cliff): +def synth_clifford_depth_lnn(cliff): """Synthesis of a Clifford into layers for linear-nearest neighbour connectivity. The depth of the synthesized n-qubit circuit is bounded by 9*n+4, which is not optimal. - It should be replaced by a better algorithm that provides a depth bounded by 7*n+2. + It should be replaced by a better algorithm that provides a depth bounded by 7*n-4 [3]. Args: cliff (Clifford): a clifford operator. @@ -409,6 +409,10 @@ def synth_clifford_layers_lnn(cliff): 2. Dmitri Maslov, Martin Roetteler, Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations, `arXiv:1705.09176 `_. + 3. Dmitri Maslov, Willers Yang, + CNOT circuits need little help to implement arbitrary Hadamard-free Clifford + transformations they generate + `arXiv:2210.16195 `_. """ circ = synth_clifford_layers( cliff, diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index e463598087e4..96436ca3fec6 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -21,7 +21,7 @@ from qiskit.test import QiskitTestCase from qiskit.quantum_info.operators import Clifford from qiskit.quantum_info import random_clifford -from qiskit.synthesis.clifford import synth_clifford_layers, synth_clifford_layers_lnn +from qiskit.synthesis.clifford import synth_clifford_layers, synth_clifford_depth_lnn from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity, get_circ_cx_depth @@ -57,7 +57,7 @@ def test_decompose_lnn_depth(self, num_qubits): samples = 10 for _ in range(samples): cliff = random_clifford(num_qubits, seed=rng) - circ = synth_clifford_layers_lnn(cliff) + circ = synth_clifford_depth_lnn(cliff) # Check that the Clifford circuit 2-qubit depth is bounded by 9*n+4 self.assertTrue(get_circ_cx_depth(circ.decompose()) <= 9 * num_qubits + 4) # Check that the Clifford circuit has linear nearest neighbour connectivity From ea2836098e493e404024a215731be88c071b27fe Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 9 Feb 2023 08:11:36 +0000 Subject: [PATCH 14/23] update references --- qiskit/synthesis/clifford/clifford_decompose_layers.py | 7 +++---- qiskit/synthesis/linear/cz_depth_lnn.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index fdd5004dc237..9c6286a77425 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -407,11 +407,10 @@ def synth_clifford_depth_lnn(cliff): structure of the Clifford group*, `arXiv:2003.09412 [quant-ph] `_ 2. Dmitri Maslov, Martin Roetteler, - Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations, + *Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations*, `arXiv:1705.09176 `_. - 3. Dmitri Maslov, Willers Yang, - CNOT circuits need little help to implement arbitrary Hadamard-free Clifford - transformations they generate + 3. Dmitri Maslov, Willers Yang, *CNOT circuits need little help to implement arbitrary + Hadamard-free Clifford transformations they generate*, `arXiv:2210.16195 `_. """ circ = synth_clifford_layers( diff --git a/qiskit/synthesis/linear/cz_depth_lnn.py b/qiskit/synthesis/linear/cz_depth_lnn.py index eb94abc7ac4e..ee0e9741a609 100644 --- a/qiskit/synthesis/linear/cz_depth_lnn.py +++ b/qiskit/synthesis/linear/cz_depth_lnn.py @@ -153,7 +153,7 @@ def synth_cz_depth_line_mr(mat: np.ndarray): Reference: 1. Dmitri Maslov, Martin Roetteler, - Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations, + *Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations*, `arXiv:1705.09176 `_. """ num_qubits = mat.shape[0] From 71a2416e5236dba0d2f94660cb83f60a2dba9b44 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 9 Feb 2023 08:13:06 +0000 Subject: [PATCH 15/23] remove a util function that is not needed --- .../synthesis/linear/linear_circuits_utils.py | 23 ------------------- .../test_clifford_decompose_layers.py | 7 ++++-- test/python/synthesis/test_cz_synthesis.py | 5 ++-- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index 63c59c4157d2..d1eda40e1f05 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -102,29 +102,6 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b return best_qc -def get_circ_cx_depth(qc: QuantumCircuit) -> int: - """Calculate the circuit depth only for CX gates, removing single qubit gates. - - Args: - qc: a QuantumCircuit containing only CX and single qubit gates. - - Returns: - int: the circuit depth only for CX gates. - - Raises: - CircuitError: if qc has a non-CX two-qubit gate. - """ - num_qubits = qc.num_qubits - qc2 = QuantumCircuit(num_qubits) - for instruction in reversed(qc.data): - if instruction.operation.num_qubits > 1: - if instruction.operation.name == "cx": - qc2._append(instruction) - else: - raise CircuitError("The circuit has two-qubits gates different than CX.") - return qc2.depth() - - def check_lnn_connectivity(qc: QuantumCircuit) -> bool: """Check that the synthesized circuit qc fits linear nearest neighbor connectivity. diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index 96436ca3fec6..2cd2a9ffb78c 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -22,7 +22,7 @@ from qiskit.quantum_info.operators import Clifford from qiskit.quantum_info import random_clifford from qiskit.synthesis.clifford import synth_clifford_layers, synth_clifford_depth_lnn -from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity, get_circ_cx_depth +from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity @ddt @@ -59,7 +59,10 @@ def test_decompose_lnn_depth(self, num_qubits): cliff = random_clifford(num_qubits, seed=rng) circ = synth_clifford_depth_lnn(cliff) # Check that the Clifford circuit 2-qubit depth is bounded by 9*n+4 - self.assertTrue(get_circ_cx_depth(circ.decompose()) <= 9 * num_qubits + 4) + depth2q = (circ.decompose()).depth( + filter_function=lambda x: x.operation.num_qubits == 2 + ) + self.assertTrue(depth2q <= 9 * num_qubits + 4) # Check that the Clifford circuit has linear nearest neighbour connectivity self.assertTrue(check_lnn_connectivity(circ.decompose())) cliff_target = Clifford(circ) diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index 314f2e19c2ea..09417c354619 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -19,7 +19,7 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import Permutation from qiskit.synthesis.linear import synth_cz_depth_line_mr -from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity, get_circ_cx_depth +from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity from qiskit.quantum_info import Clifford from qiskit.test import QiskitTestCase @@ -52,7 +52,8 @@ def test_cz_synth_lnn(self, num_qubits): qc = synth_cz_depth_line_mr(mat) # Check that the output circuit 2-qubit depth equals to 2*n+2 - self.assertTrue(get_circ_cx_depth(qc) == 2 * num_qubits + 2) + depth2q = qc.depth(filter_function=lambda x: x.operation.num_qubits == 2) + self.assertTrue(depth2q == 2 * num_qubits + 2) # Check that the output circuit has LNN connectivity self.assertTrue(check_lnn_connectivity(qc)) # Assert that we get the same element, up to reverse order of qubits From fe0e0a3a35b86fac288b775995fdc9b344cdc206 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 9 Feb 2023 10:58:56 +0000 Subject: [PATCH 16/23] update docstring --- qiskit/synthesis/linear/cz_depth_lnn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/synthesis/linear/cz_depth_lnn.py b/qiskit/synthesis/linear/cz_depth_lnn.py index ee0e9741a609..0f1117da90f0 100644 --- a/qiskit/synthesis/linear/cz_depth_lnn.py +++ b/qiskit/synthesis/linear/cz_depth_lnn.py @@ -148,8 +148,8 @@ def synth_cz_depth_line_mr(mat: np.ndarray): mat[i][j]=1 for i Date: Thu, 9 Feb 2023 10:59:59 +0000 Subject: [PATCH 17/23] add release notes --- ...fford_cz_lnn_synthesis-4b0328b581749df5.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml diff --git a/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml b/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml new file mode 100644 index 000000000000..eff4643d119e --- /dev/null +++ b/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + Add a new synthesis algorithm `.synth_cz_depth_line_mr` of a CZ circuit + for linear nearest neighbor (LNN) connectivity in 2-qubit depth of 2n+2 + using CX and phase gates (S, Sdg or Z). The synthesized circuit reverts + the order of the qubits. The synthesis algorithm is based on the paper of Maslov and Roetteler + (https://arxiv.org/abs/1705.09176). + Add a new synthesis algorithm `.synth_clifford_depth_lnn` of a Clifford circuit + for LNN connectivity in 2-qubit depth of 9n+4 (which is still not optimal), + using the layered Clifford synthesis (`.synth_clifford_layers`), + `.synth_cnot_depth_line_kms` to synthesize the CX layer in depth 5n, + and `.synth_cz_depth_line_mr` to synthesize each of the CZ layers in depth 2n+2. + This PR will be followed by another PR based on the recent paper of Maslov and Yang + (https://arxiv.org/abs/2210.16195), that synthesize the CX-CZ layers in depth 5n + for LNN connectivity and performs further optimization, and hence reduce the depth + of a Clifford circuit to 7n-4 for LNN connectivity. From d00cf998cc8ec48c88b95a64ede37c1b8fcacc8d Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Fri, 10 Feb 2023 08:56:29 +0000 Subject: [PATCH 18/23] replace int(n/2) by n//2 --- qiskit/synthesis/linear/cz_depth_lnn.py | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/qiskit/synthesis/linear/cz_depth_lnn.py b/qiskit/synthesis/linear/cz_depth_lnn.py index 0f1117da90f0..51bb6de18f9a 100644 --- a/qiskit/synthesis/linear/cz_depth_lnn.py +++ b/qiskit/synthesis/linear/cz_depth_lnn.py @@ -27,18 +27,18 @@ def _append_cx_stage1(qc, n): """A single layer of CX gates.""" - for i in range(int(n / 2)): + for i in range(n // 2): qc.cx(2 * i, 2 * i + 1) - for i in range(int((n + 1) / 2) - 1): + for i in range((n + 1) // 2 - 1): qc.cx(2 * i + 2, 2 * i + 1) return qc def _append_cx_stage2(qc, n): """A single layer of CX gates.""" - for i in range(int(n / 2)): + for i in range(n // 2): qc.cx(2 * i + 1, 2 * i) - for i in range(int((n + 1) / 2) - 1): + for i in range((n + 1) // 2 - 1): qc.cx(2 * i + 1, 2 * i + 2) return qc @@ -56,10 +56,10 @@ def _odd_pattern1(n): """ pat = [] pat.append(n - 2) - for i in range(int((n - 3) / 2)): + for i in range((n - 3) // 2): pat.append(n - 2 * i - 4) pat.append(n - 2 * i - 4) - for i in range(int((n - 1) / 2)): + for i in range((n - 1) // 2): pat.append(2 * i) pat.append(2 * i) return pat @@ -70,10 +70,10 @@ def _odd_pattern2(n): [2, 2, 4, 4, ..., n-1, n-1, n-2, n-2, n-4, n-4, ..., 5, 5, 3, 3, 1] """ pat = [] - for i in range(int((n - 1) / 2)): + for i in range((n - 1) // 2): pat.append(2 * i + 2) pat.append(2 * i + 2) - for i in range(int((n - 3) / 2)): + for i in range((n - 3) // 2): pat.append(n - 2 * i - 2) pat.append(n - 2 * i - 2) pat.append(1) @@ -86,10 +86,10 @@ def _even_pattern1(n): """ pat = [] pat.append(n - 1) - for i in range(int((n - 2) / 2)): + for i in range((n - 2) // 2): pat.append(n - 2 * i - 3) pat.append(n - 2 * i - 3) - for i in range(int((n - 2) / 2)): + for i in range((n - 2) // 2): pat.append(2 * i) pat.append(2 * i) pat.append(n - 2) @@ -101,10 +101,10 @@ def _even_pattern2(n): [2, 2, 4, 4, ..., n-2, n-2, n-1, n-1, ..., 3, 3, 1, 1] """ pat = [] - for i in range(int((n - 2) / 2)): + for i in range((n - 2) // 2): pat.append(2 * (i + 1)) pat.append(2 * (i + 1)) - for i in range(int(n / 2)): + for i in range(n // 2): pat.append(n - 2 * i - 1) pat.append(n - 2 * i - 1) return pat @@ -125,11 +125,11 @@ def _create_patterns(n): pats[(0, i)] = (i, i) if (n % 2) == 0: - ind1 = int((2 * n - 4) / 2) + ind1 = (2 * n - 4) // 2 else: - ind1 = int((2 * n - 4) / 2 - 1) + ind1 = (2 * n - 4) // 2 - 1 ind2 = 0 - while layer < int(n / 2): + while layer < (n // 2): for i in range(n): pats[(layer + 1, i)] = (pat1[ind1 + i], pat2[ind2 + i]) layer += 1 @@ -171,7 +171,7 @@ def synth_cz_depth_line_mr(mat: np.ndarray): patlist.append((i + 1, j - 1)) patlist.append((i + 1, j)) - for i in range(int((num_qubits + 1) / 2)): + for i in range((num_qubits + 1) // 2): for j in range(num_qubits): if pats[(i, j)] in patlist: patcnt = patlist.count(pats[(i, j)]) @@ -180,7 +180,7 @@ def synth_cz_depth_line_mr(mat: np.ndarray): qc = _append_cx_stage(qc, num_qubits) if (num_qubits % 2) == 0: - i = int(num_qubits / 2) + i = num_qubits // 2 for j in range(num_qubits): if pats[(i, j)] in patlist and pats[(i, j)][0] != pats[(i, j)][1]: patcnt = patlist.count(pats[(i, j)]) From 134d3f4ea4dc5bbfe5b5d9f433f7b0787600499f Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Mon, 13 Feb 2023 09:37:45 +0000 Subject: [PATCH 19/23] reduce the number of 1-qubit gates --- qiskit/synthesis/linear/cz_depth_lnn.py | 31 +++++++++++++++++----- test/python/synthesis/test_cz_synthesis.py | 2 +- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/qiskit/synthesis/linear/cz_depth_lnn.py b/qiskit/synthesis/linear/cz_depth_lnn.py index 51bb6de18f9a..38abd8141a55 100644 --- a/qiskit/synthesis/linear/cz_depth_lnn.py +++ b/qiskit/synthesis/linear/cz_depth_lnn.py @@ -45,8 +45,8 @@ def _append_cx_stage2(qc, n): def _append_cx_stage(qc, n): """Append a depth 2 layer of CX gates.""" - qc.compose(_append_cx_stage1(qc, n)) - qc.compose(_append_cx_stage2(qc, n)) + qc = _append_cx_stage1(qc, n) + qc = _append_cx_stage2(qc, n) return qc @@ -159,13 +159,15 @@ def synth_cz_depth_line_mr(mat: np.ndarray): num_qubits = mat.shape[0] pats = _create_patterns(num_qubits) patlist = [] + # s_gates[i] = 0, 1, 2 or 3 for a gate id, sdg, z or s on qubit i respectively + s_gates = np.zeros(num_qubits) qc = QuantumCircuit(num_qubits) for i in range(num_qubits): for j in range(i + 1, num_qubits): if mat[i][j]: # CZ(i,j) gate - qc.z(i) - qc.z(j) + s_gates[i] += 2 # qc.z[i] + s_gates[j] += 2 # qc.z[j] patlist.append((i, j - 1)) patlist.append((i, j)) patlist.append((i + 1, j - 1)) @@ -176,8 +178,17 @@ def synth_cz_depth_line_mr(mat: np.ndarray): if pats[(i, j)] in patlist: patcnt = patlist.count(pats[(i, j)]) for _ in range(patcnt): - qc.sdg(j) + s_gates[j] += 1 # qc.sdg[j] + # Add phase gates: s, sdg or z + for j in range(num_qubits): + if s_gates[j] % 4 == 1: + qc.sdg(j) + elif s_gates[j] % 4 == 2: + qc.z(j) + elif s_gates[j] % 4 == 3: + qc.s(j) qc = _append_cx_stage(qc, num_qubits) + s_gates = np.zeros(num_qubits) if (num_qubits % 2) == 0: i = num_qubits // 2 @@ -185,7 +196,15 @@ def synth_cz_depth_line_mr(mat: np.ndarray): if pats[(i, j)] in patlist and pats[(i, j)][0] != pats[(i, j)][1]: patcnt = patlist.count(pats[(i, j)]) for _ in range(patcnt): - qc.sdg(j) + s_gates[j] += 1 # qc.sdg[j] + # Add phase gates: s, sdg or z + for j in range(num_qubits): + if s_gates[j] % 4 == 1: + qc.sdg(j) + elif s_gates[j] % 4 == 2: + qc.z(j) + elif s_gates[j] % 4 == 3: + qc.s(j) qc = _append_cx_stage1(qc, num_qubits) return qc diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index 09417c354619..2230e4b28d3c 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -28,7 +28,7 @@ class TestCZSynth(QiskitTestCase): """Test the linear reversible circuit synthesis functions.""" - @combine(num_qubits=[4, 5, 6, 7]) + @combine(num_qubits=[2, 3, 4, 5, 6, 7]) def test_cz_synth_lnn(self, num_qubits): """Test the CZ synthesis code for linear nearest neighbour connectivity.""" seed = 1234 From 792479605c9f0a7eb70a4e5cfb88ddd025b19687 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Mon, 13 Feb 2023 10:12:31 +0000 Subject: [PATCH 20/23] fix test --- test/python/synthesis/test_cz_synthesis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index 2230e4b28d3c..65441a982418 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -28,7 +28,7 @@ class TestCZSynth(QiskitTestCase): """Test the linear reversible circuit synthesis functions.""" - @combine(num_qubits=[2, 3, 4, 5, 6, 7]) + @combine(num_qubits=[3, 4, 5, 6, 7]) def test_cz_synth_lnn(self, num_qubits): """Test the CZ synthesis code for linear nearest neighbour connectivity.""" seed = 1234 @@ -41,8 +41,8 @@ def test_cz_synth_lnn(self, num_qubits): # Generate a random CZ circuit for _ in range(num_gates): - i = rng.integers(num_qubits - 1) - j = rng.integers(num_qubits - 1) + i = rng.integers(num_qubits) + j = rng.integers(num_qubits) if i != j: qctest.cz(i, j) if j > i: From 8e30f2ee1607785473ea30939f81005c840da40c Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 16 Feb 2023 08:14:07 +0000 Subject: [PATCH 21/23] refactor and minor changes following review --- qiskit/synthesis/__init__.py | 14 +++++++--- .../clifford/clifford_decompose_layers.py | 28 +++++++++++-------- qiskit/synthesis/linear/__init__.py | 6 ++-- qiskit/synthesis/linear_phase/__init__.py | 15 ++++++++++ .../{linear => linear_phase}/cz_depth_lnn.py | 13 +++------ ...ord_cz_lnn_synthesis-4b0328b581749df5.yaml | 5 ++-- test/python/synthesis/test_cz_synthesis.py | 4 +-- 7 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 qiskit/synthesis/linear_phase/__init__.py rename qiskit/synthesis/{linear => linear_phase}/cz_depth_lnn.py (95%) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 7311b9a51126..7806dee2cc32 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2020. +# (C) Copyright IBM 2017 - 2023. # # 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 @@ -30,13 +30,19 @@ MatrixExponential QDrift -Linear Function and Linear-Phase Synthesis -========================================== +Linear Function +=============== .. autosummary:: :toctree: ../stubs/ synth_cnot_count_full_pmh synth_cnot_depth_line_kms + +Linear-Phase Synthesis +====================== +.. autosummary:: + :toctree: ../stubs/ + synth_cz_depth_line_mr Permutation Synthesis @@ -99,8 +105,8 @@ from .linear import ( synth_cnot_count_full_pmh, synth_cnot_depth_line_kms, - synth_cz_depth_line_mr, ) +from .linear_phase import synth_cz_depth_line_mr from .clifford import ( synth_clifford_full, synth_clifford_ag, diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 9c6286a77425..16e538e5086a 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -21,8 +21,9 @@ from qiskit.synthesis.linear import ( synth_cnot_count_full_pmh, synth_cnot_depth_line_kms, - synth_cz_depth_line_mr, ) +from qiskit.synthesis.linear_phase import synth_cz_depth_line_mr + from qiskit.synthesis.linear.linear_matrix_utils import ( calc_inverse_matrix, _compute_rank, @@ -65,7 +66,7 @@ def synth_clifford_layers( cx_synth_func=_default_cx_synth_func, cz_synth_func=_default_cz_synth_func, cx_cz_synth_func=None, - reverse_qubits=False, + cz_func_reverse_qubits=False, validate=False, ): """Synthesis of a Clifford into layers, it provides a similar decomposition to the synthesis @@ -86,13 +87,18 @@ def synth_clifford_layers( q_4: ┤4 ├┤4 ├┤4 ├┤4 ├┤4 ├┤4 ├┤4 ├┤4 ├ └─────┘└─────┘└────────┘└─────┘└─────┘└─────┘└─────┘└────────┘ + This decomposition is for the default cz_synth_func and cx_synth_func functions, + with other functions one may see slightly different decomposition. + Args: cliff (Clifford): a clifford operator. cx_synth_func (Callable): a function to decompose the CX sub-circuit. + It gets as input a boolean invertible matrix, and outputs a QuantumCircuit. cz_synth_func (Callable): a function to decompose the CZ sub-circuit. + It gets as input a boolean symmetric matrix, and outputs a QuantumCircuit. cx_cz_synth_func (Callable): optional, a function to decompose both sub-circuits CZ and CX. validate (Boolean): if True, validates the synthesis process. - reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr, + cz_func_reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr, since this function returns a circuit that inverts the order of qubits. Return: @@ -105,7 +111,7 @@ def synth_clifford_layers( """ num_qubits = cliff.num_qubits - if reverse_qubits: + if cz_func_reverse_qubits: cliff0 = _reverse_clifford(cliff) else: cliff0 = cliff @@ -125,7 +131,7 @@ def synth_clifford_layers( cz_synth_func=cz_synth_func, cx_synth_func=cx_synth_func, cx_cz_synth_func=cx_cz_synth_func, - reverse_qubits=reverse_qubits, + cz_func_reverse_qubits=cz_func_reverse_qubits, ) layeredCircuit.append(S2_circ, qubit_list) @@ -138,7 +144,7 @@ def synth_clifford_layers( layeredCircuit.append(S1_circ, qubit_list) layeredCircuit.append(CZ1_circ, qubit_list) - if reverse_qubits: + if cz_func_reverse_qubits: H1_circ = H1_circ.reverse_bits() layeredCircuit.append(H1_circ, qubit_list) @@ -293,7 +299,7 @@ def _decompose_graph_state(cliff, validate, cz_synth_func): def _decompose_hadamard_free( - cliff, validate, cz_synth_func, cx_synth_func, cx_cz_synth_func, reverse_qubits + cliff, validate, cz_synth_func, cx_synth_func, cx_cz_synth_func, cz_func_reverse_qubits ): """Assumes that the Clifford cliff is Hadamard free. Decompose it into the layers S2 - CZ2 - CX, where @@ -307,7 +313,7 @@ def _decompose_hadamard_free( cz_synth_func (Callable): a function to decompose the CZ sub-circuit. cx_synth_func (Callable): a function to decompose the CX sub-circuit. cx_cz_synth_func (Callable): optional, a function to decompose both sub-circuits CZ and CX. - reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr. + cz_func_reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr. Return: S2_circ: a circuit that can contain only S gates. @@ -349,7 +355,7 @@ def _decompose_hadamard_free( CZ2_circ = cz_synth_func(destabz_update) mat = destabx.transpose() - if reverse_qubits: + if cz_func_reverse_qubits: mat = np.flip(mat, axis=0) CX_circ = cx_synth_func(mat) @@ -394,7 +400,7 @@ def synth_clifford_depth_lnn(cliff): """Synthesis of a Clifford into layers for linear-nearest neighbour connectivity. The depth of the synthesized n-qubit circuit is bounded by 9*n+4, which is not optimal. - It should be replaced by a better algorithm that provides a depth bounded by 7*n-4 [3]. + It should be replaced by a better algorithm that provides depth bounded by 7*n-4 [3]. Args: cliff (Clifford): a clifford operator. @@ -417,6 +423,6 @@ def synth_clifford_depth_lnn(cliff): cliff, cx_synth_func=synth_cnot_depth_line_kms, cz_synth_func=synth_cz_depth_line_mr, - reverse_qubits=True, + cz_func_reverse_qubits=True, ) return circ diff --git a/qiskit/synthesis/linear/__init__.py b/qiskit/synthesis/linear/__init__.py index a5940d85a2b2..7300ee8e6f0a 100644 --- a/qiskit/synthesis/linear/__init__.py +++ b/qiskit/synthesis/linear/__init__.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) Copyright IBM 2017 - 2023. # # 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 @@ -10,11 +10,9 @@ # 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.""" - +"""Module containing cnot circuits""" from .graysynth import graysynth, synth_cnot_count_full_pmh -from .cz_depth_lnn import synth_cz_depth_line_mr 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_phase/__init__.py b/qiskit/synthesis/linear_phase/__init__.py new file mode 100644 index 000000000000..78ce4433603f --- /dev/null +++ b/qiskit/synthesis/linear_phase/__init__.py @@ -0,0 +1,15 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017 - 2023. +# +# 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-phase circuits""" + +from .cz_depth_lnn import synth_cz_depth_line_mr diff --git a/qiskit/synthesis/linear/cz_depth_lnn.py b/qiskit/synthesis/linear_phase/cz_depth_lnn.py similarity index 95% rename from qiskit/synthesis/linear/cz_depth_lnn.py rename to qiskit/synthesis/linear_phase/cz_depth_lnn.py index 38abd8141a55..1d344ed860df 100644 --- a/qiskit/synthesis/linear/cz_depth_lnn.py +++ b/qiskit/synthesis/linear_phase/cz_depth_lnn.py @@ -11,9 +11,10 @@ # that they have been altered from the originals. """ -Optimize the synthesis of an n-qubit circuit contains only CZ gates for +Synthesis of an n-qubit circuit containing only CZ gates for linear nearest neighbor (LNN) connectivity, using CX and phase (S, Sdg or Z) gates. The two-qubit depth of the circuit is bounded by 2*n+2. +This algorithm reverts the order of qubits. References: [1]: Dmitri Maslov, Martin Roetteler, @@ -43,13 +44,6 @@ def _append_cx_stage2(qc, n): return qc -def _append_cx_stage(qc, n): - """Append a depth 2 layer of CX gates.""" - qc = _append_cx_stage1(qc, n) - qc = _append_cx_stage2(qc, n) - return qc - - def _odd_pattern1(n): """A pattern denoted by Pj in [1] for odd number of qubits: [n-2, n-4, n-4, ..., 3, 3, 1, 1, 0, 0, 2, 2, ..., n-3, n-3] @@ -187,7 +181,8 @@ def synth_cz_depth_line_mr(mat: np.ndarray): qc.z(j) elif s_gates[j] % 4 == 3: qc.s(j) - qc = _append_cx_stage(qc, num_qubits) + qc = _append_cx_stage1(qc, num_qubits) + qc = _append_cx_stage2(qc, num_qubits) s_gates = np.zeros(num_qubits) if (num_qubits % 2) == 0: diff --git a/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml b/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml index eff4643d119e..03f880559a66 100644 --- a/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml +++ b/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml @@ -6,12 +6,13 @@ features: using CX and phase gates (S, Sdg or Z). The synthesized circuit reverts the order of the qubits. The synthesis algorithm is based on the paper of Maslov and Roetteler (https://arxiv.org/abs/1705.09176). + - | Add a new synthesis algorithm `.synth_clifford_depth_lnn` of a Clifford circuit for LNN connectivity in 2-qubit depth of 9n+4 (which is still not optimal), using the layered Clifford synthesis (`.synth_clifford_layers`), `.synth_cnot_depth_line_kms` to synthesize the CX layer in depth 5n, and `.synth_cz_depth_line_mr` to synthesize each of the CZ layers in depth 2n+2. This PR will be followed by another PR based on the recent paper of Maslov and Yang - (https://arxiv.org/abs/2210.16195), that synthesize the CX-CZ layers in depth 5n - for LNN connectivity and performs further optimization, and hence reduce the depth + (https://arxiv.org/abs/2210.16195), that synthesizes the CX-CZ layers in depth 5n + for LNN connectivity and performs further optimization, and hence reduces the depth of a Clifford circuit to 7n-4 for LNN connectivity. diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index 65441a982418..6db88da039d5 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2022. +# (C) Copyright IBM 2023. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -18,7 +18,7 @@ from ddt import ddt from qiskit import QuantumCircuit from qiskit.circuit.library import Permutation -from qiskit.synthesis.linear import synth_cz_depth_line_mr +from qiskit.synthesis.linear_phase import synth_cz_depth_line_mr from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity from qiskit.quantum_info import Clifford from qiskit.test import QiskitTestCase From 29e90b38144867f3d6037eb7147280ec30f421a5 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Sun, 19 Feb 2023 08:16:03 +0000 Subject: [PATCH 22/23] update docs --- .../synthesis/clifford/clifford_decompose_layers.py | 2 +- qiskit/synthesis/linear_phase/cz_depth_lnn.py | 5 +++-- .../clifford_cz_lnn_synthesis-4b0328b581749df5.yaml | 12 ++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 16e538e5086a..f4f8a1f6c7b3 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -99,7 +99,7 @@ def synth_clifford_layers( cx_cz_synth_func (Callable): optional, a function to decompose both sub-circuits CZ and CX. validate (Boolean): if True, validates the synthesis process. cz_func_reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr, - since this function returns a circuit that inverts the order of qubits. + since this function returns a circuit that reverts the order of qubits. Return: QuantumCircuit: a circuit implementation of the Clifford. diff --git a/qiskit/synthesis/linear_phase/cz_depth_lnn.py b/qiskit/synthesis/linear_phase/cz_depth_lnn.py index 1d344ed860df..9ddf71f525d4 100644 --- a/qiskit/synthesis/linear_phase/cz_depth_lnn.py +++ b/qiskit/synthesis/linear_phase/cz_depth_lnn.py @@ -135,7 +135,9 @@ def _create_patterns(n): def synth_cz_depth_line_mr(mat: np.ndarray): """Synthesis of a CZ circuit for linear nearest neighbour (LNN) connectivity, based on Maslov and Roetteler. - Note that this method *reverts* the order of qubits in the circuit. + + Note that this method *reverts* the order of qubits in the circuit, + and returns a circuit containing CX and phase (S, Sdg or Z) gates. Args: mat: an upper-diagonal matrix representing the CZ circuit. @@ -143,7 +145,6 @@ def synth_cz_depth_line_mr(mat: np.ndarray): Return: QuantumCircuit: a circuit implementation of the CZ circuit of depth 2*n+2 for LNN connectivity. - The circuit *reverts* the order of qubits, and contains CX and phase (S, Sdg or Z) gates. Reference: 1. Dmitri Maslov, Martin Roetteler, diff --git a/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml b/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml index 03f880559a66..90b3b2b0914a 100644 --- a/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml +++ b/releasenotes/notes/clifford_cz_lnn_synthesis-4b0328b581749df5.yaml @@ -1,17 +1,17 @@ --- features: - | - Add a new synthesis algorithm `.synth_cz_depth_line_mr` of a CZ circuit + Add a new synthesis algorithm :class:`.synth_cz_depth_line_mr` of a CZ circuit for linear nearest neighbor (LNN) connectivity in 2-qubit depth of 2n+2 using CX and phase gates (S, Sdg or Z). The synthesized circuit reverts the order of the qubits. The synthesis algorithm is based on the paper of Maslov and Roetteler (https://arxiv.org/abs/1705.09176). - - | - Add a new synthesis algorithm `.synth_clifford_depth_lnn` of a Clifford circuit + - | + Add a new synthesis algorithm :class:`.synth_clifford_depth_lnn` of a Clifford circuit for LNN connectivity in 2-qubit depth of 9n+4 (which is still not optimal), - using the layered Clifford synthesis (`.synth_clifford_layers`), - `.synth_cnot_depth_line_kms` to synthesize the CX layer in depth 5n, - and `.synth_cz_depth_line_mr` to synthesize each of the CZ layers in depth 2n+2. + using the layered Clifford synthesis (:class:`.synth_clifford_layers`), + :class:`.synth_cnot_depth_line_kms` to synthesize the CX layer in depth 5n, + and :class:`.synth_cz_depth_line_mr` to synthesize each of the CZ layers in depth 2n+2. This PR will be followed by another PR based on the recent paper of Maslov and Yang (https://arxiv.org/abs/2210.16195), that synthesizes the CX-CZ layers in depth 5n for LNN connectivity and performs further optimization, and hence reduces the depth From dfb4d386a47b49bba613666b29bb0365d6f326b7 Mon Sep 17 00:00:00 2001 From: Shelly Garion Date: Thu, 23 Feb 2023 11:07:29 +0000 Subject: [PATCH 23/23] update init file following review --- qiskit/synthesis/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 7806dee2cc32..c90afbb6284e 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -30,8 +30,8 @@ MatrixExponential QDrift -Linear Function -=============== +Linear Function Synthesis +========================= .. autosummary:: :toctree: ../stubs/