Skip to content

Commit

Permalink
Synthesis of CZ and Clifford circuits for linear nearest neighbor con…
Browse files Browse the repository at this point in the history
…nectivity (#9450)

* add cz synthesis for LNN

* basic cz synthesis test

* adjust clifford_decompose_layers to cz synthesis for LNN

* add a basic test for layered clifford synthesis for LNN

* enhance the tests

* fix lint, check function name

* Add utils needed for LNN tests

* add tests for LNN and depth

* Add documentation for cz synthesis

* fix misprint

* add synth_clifford_depth_lnn

* updates following review

* rename synth_clifford_depth_lnn

* update references

* remove a util function that is not needed

* update docstring

* add release notes

* replace int(n/2) by n//2

* reduce the number of 1-qubit gates

* fix test

* refactor and minor changes following review

* update docs

* update init file following review

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
ShellyGarion and mergify[bot] authored Feb 23, 2023
1 parent e52207f commit 158563f
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 34 deletions.
17 changes: 15 additions & 2 deletions qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -38,6 +38,13 @@
synth_cnot_count_full_pmh
synth_cnot_depth_line_kms
Linear-Phase Synthesis
======================
.. autosummary::
:toctree: ../stubs/
synth_cz_depth_line_mr
Permutation Synthesis
=====================
Expand All @@ -59,6 +66,7 @@
synth_clifford_bm
synth_clifford_greedy
synth_clifford_layers
synth_clifford_depth_lnn
CNOTDihedral Synthesis
======================
Expand Down Expand Up @@ -95,13 +103,18 @@
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,
)
from .linear_phase import synth_cz_depth_line_mr
from .clifford import (
synth_clifford_full,
synth_clifford_ag,
synth_clifford_bm,
synth_clifford_greedy,
synth_clifford_layers,
synth_clifford_depth_lnn,
)
from .cnotdihedral import (
synth_cnotdihedral_full,
Expand Down
2 changes: 1 addition & 1 deletion qiskit/synthesis/clifford/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
91 changes: 64 additions & 27 deletions qiskit/synthesis/clifford/clifford_decompose_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@
# 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_cnot_depth_line_kms,
)
from qiskit.synthesis.linear_phase import synth_cz_depth_line_mr

from qiskit.synthesis.linear.linear_matrix_utils import (
calc_inverse_matrix,
check_invertible_binary_matrix,
_compute_rank,
_gauss_elimination,
_gauss_elimination_with_perm,
Expand All @@ -32,24 +37,17 @@
)


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.")

CX_circ = synth_cnot_count_full_pmh(mat)
CX_circ.name = "CX"

if validate:
_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.
"""
Expand All @@ -60,9 +58,6 @@ def _default_cz_synth_func(symmetric_mat, validate):
for i in range(0, j):
if symmetric_mat[i][j]:
qc.cz(i, j)

if validate:
_check_gates(qc, "cz")
return qc


Expand All @@ -71,10 +66,11 @@ def synth_clifford_layers(
cx_synth_func=_default_cx_synth_func,
cz_synth_func=_default_cz_synth_func,
cx_cz_synth_func=None,
cz_func_reverse_qubits=False,
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:
Expand All @@ -91,12 +87,19 @@ 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.
cz_func_reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr,
since this function returns a circuit that reverts the order of qubits.
Return:
QuantumCircuit: a circuit implementation of the Clifford.
Expand All @@ -108,11 +111,15 @@ def synth_clifford_layers(
"""

num_qubits = cliff.num_qubits
if cz_func_reverse_qubits:
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
Expand All @@ -124,6 +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,
cz_func_reverse_qubits=cz_func_reverse_qubits,
)

layeredCircuit.append(S2_circ, qubit_list)
Expand All @@ -136,6 +144,8 @@ def synth_clifford_layers(
layeredCircuit.append(S1_circ, qubit_list)
layeredCircuit.append(CZ1_circ, qubit_list)

if cz_func_reverse_qubits:
H1_circ = H1_circ.reverse_bits()
layeredCircuit.append(H1_circ, qubit_list)

# Add Pauli layer to fix the Clifford phase signs
Expand Down Expand Up @@ -269,7 +279,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):
Expand All @@ -288,7 +298,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, cz_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,
Expand All @@ -301,6 +313,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.
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.
Expand Down Expand Up @@ -339,10 +352,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_func_reverse_qubits:
mat = np.flip(mat, axis=0)
CX_circ = cx_synth_func(mat)

return S2_circ, CZ2_circ, CX_circ

Expand Down Expand Up @@ -381,11 +396,33 @@ 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
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 depth bounded by 7*n-4 [3].
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] <https://arxiv.org/abs/2003.09412>`_
2. Dmitri Maslov, Martin Roetteler,
*Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations*,
`arXiv:1705.09176 <https://arxiv.org/abs/1705.09176>`_.
3. Dmitri Maslov, Willers Yang, *CNOT circuits need little help to implement arbitrary
Hadamard-free Clifford transformations they generate*,
`arXiv:2210.16195 <https://arxiv.org/abs/2210.16195>`_.
"""
for inst, _, _ in qc.data:
if not inst.name in allowed_gates:
raise QiskitError("The gate name is not in the allowed_gates list.")
circ = synth_clifford_layers(
cliff,
cx_synth_func=synth_cnot_depth_line_kms,
cz_synth_func=synth_cz_depth_line_mr,
cz_func_reverse_qubits=True,
)
return circ
5 changes: 2 additions & 3 deletions qiskit/synthesis/linear/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,8 +10,7 @@
# 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 .linear_depth_lnn import synth_cnot_depth_line_kms
Expand Down
25 changes: 25 additions & 0 deletions qiskit/synthesis/linear/linear_circuits_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,28 @@ def optimize_cx_4_options(function: Callable, mat: np.ndarray, optimize_count: b
best_qc = qc

return best_qc


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
15 changes: 15 additions & 0 deletions qiskit/synthesis/linear_phase/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 158563f

Please sign in to comment.