Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reverse permutation for LNN connectivity #12181

Merged
merged 7 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
.. autofunction:: synth_permutation_depth_lnn_kms
.. autofunction:: synth_permutation_basic
.. autofunction:: synth_permutation_acg
.. autofunction:: synth_permutation_reverse_lnn_kms

Clifford Synthesis
==================
Expand Down Expand Up @@ -140,6 +141,7 @@
synth_permutation_depth_lnn_kms,
synth_permutation_basic,
synth_permutation_acg,
synth_permutation_reverse_lnn_kms,
)
from .linear import (
synth_cnot_count_full_pmh,
Expand Down
22 changes: 4 additions & 18 deletions qiskit/synthesis/linear_phase/cz_depth_lnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,10 @@

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(n // 2):
qc.cx(2 * i, 2 * i + 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(n // 2):
qc.cx(2 * i + 1, 2 * i)
for i in range((n + 1) // 2 - 1):
qc.cx(2 * i + 1, 2 * i + 2)
return qc
from qiskit.synthesis.permutation.permutation_reverse_lnn import (
_append_cx_stage1,
_append_cx_stage2,
)


def _odd_pattern1(n):
Expand Down
1 change: 1 addition & 0 deletions qiskit/synthesis/permutation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@

from .permutation_lnn import synth_permutation_depth_lnn_kms
from .permutation_full import synth_permutation_basic, synth_permutation_acg
from .permutation_reverse_lnn import synth_permutation_reverse_lnn_kms
65 changes: 65 additions & 0 deletions qiskit/synthesis/permutation/permutation_reverse_lnn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024
#
# 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.
"""
Synthesis of a reverse permutation for LNN connectivity.
"""

from qiskit.circuit import QuantumCircuit


def _append_cx_stage1(qc, n):
"""A single layer of CX gates."""
for i in range(n // 2):
qc.cx(2 * i, 2 * i + 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(n // 2):
qc.cx(2 * i + 1, 2 * i)
for i in range((n + 1) // 2 - 1):
qc.cx(2 * i + 1, 2 * i + 2)
return qc
Comment on lines +19 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed _append_cx_stage1 and _append_cx_stage2 functions were moved from qiskit/synthesis/linear_phase/cz_depth_lnn.py to qiskit/synthesis/permutation/permutation_reverse_lnn.py. Could you share the reasoning behind this change?

While I understand these are private functions and the move does not impact their functionality, I am curious if the new location makes more sense and better aligns with their specific use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are helper functions to construct a reverse permutation, and this is the reason that they have been moved. CZ circuit synthesis for LNN is basically constructing a reverse permutation with phase gates in between.



def synth_permutation_reverse_lnn_kms(num_qubits: int) -> QuantumCircuit:
"""
Synthesize reverse permutation for linear nearest-neighbor architectures using
Kutin, Moulton, Smithline method.

Synthesis algorithm for reverse permutation from [1], section 5.
This algorithm synthesizes the reverse permutation on :math:`n` qubits over
a linear nearest-neighbor architecture using CX gates with depth :math:`2 * n + 2`.

Args:
num_qubits: The number of qubits.

Returns:
The synthesized quantum circuit.

References:
1. Kutin, S., Moulton, D. P., Smithline, L.,
*Computation at a distance*, Chicago J. Theor. Comput. Sci., vol. 2007, (2007),
`arXiv:quant-ph/0701194 <https://arxiv.org/abs/quant-ph/0701194>`_
"""

qc = QuantumCircuit(num_qubits)
for _ in range((num_qubits + 1) // 2):
qc = _append_cx_stage1(qc, num_qubits)
qc = _append_cx_stage2(qc, num_qubits)
if (num_qubits % 2) == 0:
qc = _append_cx_stage1(qc, num_qubits)

return qc
9 changes: 3 additions & 6 deletions qiskit/synthesis/qft/qft_decompose_lnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.synthesis.linear_phase.cz_depth_lnn import _append_cx_stage1, _append_cx_stage2
from qiskit.synthesis.permutation import synth_permutation_reverse_lnn_kms


def synth_qft_line(
Expand Down Expand Up @@ -65,10 +65,7 @@ def synth_qft_line(
if not do_swaps:
# Add a reversal network for LNN connectivity in depth 2*n+2,
# based on Kutin at al., https://arxiv.org/abs/quant-ph/0701194, Section 5.
for _ in range((num_qubits + 1) // 2):
qc = _append_cx_stage1(qc, num_qubits)
qc = _append_cx_stage2(qc, num_qubits)
if (num_qubits % 2) == 0:
qc = _append_cx_stage1(qc, num_qubits)
qc_rev = synth_permutation_reverse_lnn_kms(num_qubits)
qc = qc.compose(qc_rev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, but I think we can do this more efficiently. While the current implementation using compose ensures correctness, it might not be the most efficient in terms of performance and memory usage, especially for larger circuits.

Instead of composing two quantum circuits, we could consider enhancing efficiency by adding a function that directly appends the reverse permutation LNN to an existing circuit. This approach would modify the circuit in place, avoiding the overhead associated with creating and composing a new circuit instance. Here's a conceptual sketch of how this could be implemented:

def append_reverse_permutation_lnn_kms(qc: QuantumCircuit, num_qubits: int) -> None:
    for _ in range((num_qubits + 1) // 2):
        _append_cx_stage1(qc, num_qubits)
        _append_cx_stage2(qc, num_qubits)
    if (num_qubits % 2) == 0:
        _append_cx_stage1(qc, num_qubits)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a good suggestion, I added it as a helper function.


return qc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features_synthesis:
- |
Add a new synthesis method :func:`.synth_permutation_reverse_lnn_kms`
of reverse permutations for linear nearest-neighbor architectures using
Kutin, Moulton, Smithline method.
This algorithm synthesizes the reverse permutation on :math:`n` qubits over
a linear nearest-neighbor architecture using CX gates with depth :math:`2 * n + 2`.
28 changes: 26 additions & 2 deletions test/python/synthesis/test_permutation_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@

from qiskit.quantum_info.operators import Operator
from qiskit.circuit.library import LinearFunction, PermutationGate
from qiskit.synthesis import synth_permutation_acg
from qiskit.synthesis.permutation import synth_permutation_depth_lnn_kms, synth_permutation_basic
from qiskit.synthesis.permutation import (
synth_permutation_acg,
synth_permutation_depth_lnn_kms,
synth_permutation_basic,
synth_permutation_reverse_lnn_kms,
)
from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap
from test import QiskitTestCase # pylint: disable=wrong-import-order

Expand Down Expand Up @@ -108,6 +112,26 @@ def test_synth_permutation_depth_lnn_kms(self, width):
synthesized_pattern = LinearFunction(qc).permutation_pattern()
self.assertTrue(np.array_equal(synthesized_pattern, pattern))

@data(4, 5, 10, 15, 20)
def test_synth_permutation_reverse_lnn_kms(self, num_qubits):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on the implementation and testing. The current tests effectively assess the basic functionality and correct usage scenarios. However, I suggest adding tests that focus on:

  • Edge Cases: Like scenarios with 0 or 1 qubits.
  • Invalid Input: To ensure the system handles invalid inputs as expected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added test cases for 1,2 and 3 qubits.

"""Test synth_permutation_reverse_lnn_kms function produces the correct
circuit."""
pattern = list(reversed(range(num_qubits)))
qc = synth_permutation_reverse_lnn_kms(num_qubits)
self.assertListEqual((LinearFunction(qc).permutation_pattern()).tolist(), pattern)

# Check that the CX depth of the circuit is at 2*n+2
self.assertEqual(qc.depth(), 2 * num_qubits + 2)

# Check that the synthesized circuit consists of CX gates only,
# and that these CXs adhere to the LNN connectivity.
for instruction in qc.data:
self.assertEqual(instruction.operation.name, "cx")
q0 = qc.find_bit(instruction.qubits[0]).index
q1 = qc.find_bit(instruction.qubits[1]).index
dist = abs(q0 - q1)
self.assertEqual(dist, 1)

@data(4, 5, 6, 7)
def test_permutation_matrix(self, width):
"""Test that the unitary matrix constructed from permutation pattern
Expand Down
Loading