Skip to content

Commit

Permalink
High-level-synthesis for permutations (#9157)
Browse files Browse the repository at this point in the history
* Adding permutation synthesis algorithm for LNN

* release notes

* Checking that the synthesized permutation adheres to the LNN connectivity and has depth guaranteed by the algorithm

* Adding tests for 15 qubits

Co-authored-by: Nir Gavrielov <nirgavrielov@gmail.com>

* Changing Permutation to be a Gate rather than QuantumCircuit

* Adding the property pattern to Permutation class

* fixing assert

* improving description message for _get_ordered_swap

* applying suggestions from code review

* minor

* attempt to fix docstring

* Another attempt to fix docsting

* another attempt to fix docstring

* temporarily simplifying docstring to see if this passes docs build

* adding blank line

* another attempt

* Restoring docstring

* removing extra line

* adding __array__ method for permutation + tests

* HLS permutation plugin based on the original synthesis algorithm for permutations

* speeding up _get_ordered_swap based on review comments

* Adding depth-2 synthesis algorithm for permutations for all-to-all connectivity; including tests and plugin

* release notes

* Adding example to release notes

* Update documentation of Permutation

* add missing import

* drawing decomposed circuit with permutations

* forgot parenthesis

* restoring qasm for circuits containing Permutations

* Adding permutation method to QuantumCircuit

* Adding test for quantum circuit with permutations

* pylint

* adding inverse() method to permutations

* qpy support for permutations

* tests for quantum circuits with permutations

* checking depth bound on the ACG method

* Adding tests for new Permutation functionality

* black

* Following review, keeping the old Permutation quantum circuit for backward compatibility, and naming the new permutation gate class as PermutationGate

* additional fixes

* updating release notes

* docs fix

* Removing permutation method from QuantumCircuit

* Adding QPY test for circuits with permutation gates

* Update qiskit/circuit/quantumcircuit.py

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>

* Set default qasm name override to None

Co-authored-by: Nir Gavrielov <nirgavrielov@gmail.com>
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Jan 19, 2023
1 parent d9b1a4b commit 179c29c
Show file tree
Hide file tree
Showing 17 changed files with 593 additions and 29 deletions.
2 changes: 2 additions & 0 deletions qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
MCMT
MCMTVChain
Permutation
PermutationGate
GMS
GR
GRX
Expand Down Expand Up @@ -497,6 +498,7 @@
MCMT,
MCMTVChain,
Permutation,
PermutationGate,
GMS,
MSGate,
GR,
Expand Down
2 changes: 1 addition & 1 deletion qiskit/circuit/library/generalized_gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""The circuit library module on generalized gates."""

from .diagonal import Diagonal
from .permutation import Permutation
from .permutation import Permutation, PermutationGate
from .mcmt import MCMT, MCMTVChain
from .gms import GMS, MSGate
from .gr import GR, GRX, GRY, GRZ
Expand Down
115 changes: 114 additions & 1 deletion qiskit/circuit/library/generalized_gates/permutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Permutation circuit."""
"""Permutation circuit (the old way to specify permutations, which is required for
backward compatibility and which will be eventually deprecated) and the permutation
gate (the new way to specify permutations, allowing a variety of synthesis algorithms).
"""

from typing import List, Optional

import numpy as np

from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumcircuit import Gate
from qiskit.circuit.exceptions import CircuitError


Expand Down Expand Up @@ -88,3 +92,112 @@ def __init__(

all_qubits = self.qubits
self.append(circuit.to_gate(), all_qubits)


class PermutationGate(Gate):
"""A gate that permutes qubits."""

def __init__(
self,
pattern: List[int],
) -> None:
"""Return a permutation gate.
Args:
pattern: permutation pattern, describing which qubits occupy the
positions 0, 1, 2, etc. after applying the permutation, that
is ``pattern[k] = m`` when the permutation maps qubit ``m``
to position ``k``. As an example, the pattern ``[2, 4, 3, 0, 1]``
means that qubit ``2`` goes to position ``0``, qubit ``4``
goes to the position ``1``, etc.
Raises:
CircuitError: if permutation pattern is malformed.
Reference Circuit:
.. plot::
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.library import PermutationGate
A = [2,4,3,0,1]
permutation = PermutationGate(A)
circuit = QuantumCircuit(5)
circuit.append(permutation, [0, 1, 2, 3, 4])
circuit.draw('mpl')
Expanded Circuit:
.. plot::
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.library import PermutationGate
from qiskit.tools.jupyter.library import _generate_circuit_library_visualization
A = [2,4,3,0,1]
permutation = PermutationGate(A)
circuit = QuantumCircuit(5)
circuit.append(permutation, [0, 1, 2, 3, 4])
_generate_circuit_library_visualization(circuit.decompose())
"""
num_qubits = len(pattern)
if sorted(pattern) != list(range(num_qubits)):
raise CircuitError(
"Permutation pattern must be some ordering of 0..num_qubits-1 in a list."
)
pattern = np.array(pattern)

# This is needed to support qasm()
self._qasm_name = "permutation__" + "_".join([str(n) for n in pattern]) + "_"
self._qasm_definition = None

super().__init__(name="permutation", num_qubits=num_qubits, params=[pattern])

def __array__(self, dtype=None):
"""Return a numpy.array for the Permutation gate."""
nq = len(self.pattern)
mat = np.zeros((2**nq, 2**nq), dtype=dtype)

for r in range(2**nq):
# convert row to bitstring, reverse, apply permutation pattern, reverse again,
# and convert to row
bit = bin(r)[2:].zfill(nq)[::-1]
permuted_bit = "".join([bit[j] for j in self.pattern])
pr = int(permuted_bit[::-1], 2)
mat[pr, r] = 1

return mat

def validate_parameter(self, parameter):
"""Parameter validation."""
return parameter

@property
def pattern(self):
"""Returns the permutation pattern defining this permutation."""
return self.params[0]

def inverse(self):
"""Returns the inverse of the permutation."""

# pylint: disable=cyclic-import
from qiskit.synthesis.permutation.permutation_utils import _inverse_pattern

return PermutationGate(pattern=_inverse_pattern(self.pattern))

def qasm(self):
"""The qasm for a permutation."""

if not self._qasm_definition:

# pylint: disable=cyclic-import
from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap

# This qasm should be identical to the one produced when permutation
# was a circuit rather than a gate.
swaps = _get_ordered_swap(self.pattern)
gates_def = "".join(["swap q" + str(i) + ",q" + str(j) + "; " for i, j in swaps])
qubit_list = ",".join(["q" + str(n) for n in range(len(self.pattern))])
self._qasm_definition = (
"gate " + self._qasm_name + " " + qubit_list + " { " + gates_def + "}"
)

return self._qasmif(self._qasm_name)
37 changes: 23 additions & 14 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1698,20 +1698,29 @@ def qasm(
operation = operation.copy(name=escaped)

# decompose gate using definitions if they are not defined in OpenQASM2
if (
operation.name not in existing_gate_names
and operation not in existing_composite_circuits
):
if operation.name in [
operation.name for operation in existing_composite_circuits
]:
# append operation id to name of operation copy to make it unique
operation = operation.copy(name=f"{operation.name}_{id(operation)}")

existing_composite_circuits.append(operation)
_add_sub_instruction_to_existing_composite_circuits(
operation, existing_gate_names, existing_composite_circuits
)
if operation.name not in existing_gate_names:
op_qasm_name = None
if operation.name == "permutation":
op_qasm_name = getattr(operation, "_qasm_name", None)
if op_qasm_name:
operation = operation.copy(name=op_qasm_name)

if operation not in existing_composite_circuits:
if operation.name in [
operation.name for operation in existing_composite_circuits
]:
# append operation id to name of operation copy to make it unique
operation = operation.copy(name=f"{operation.name}_{id(operation)}")

existing_composite_circuits.append(operation)

# Strictly speaking, the code below does not work for operations that
# do not have the "definition" method but require a complex (recursive)
# "_qasm_definition". Fortunately, right now we do not have any such operations.
if getattr(operation, "definition", None) is not None:
_add_sub_instruction_to_existing_composite_circuits(
operation, existing_gate_names, existing_composite_circuits
)

# Insert qasm representation of the original instruction
string_temp += "{} {};\n".format(
Expand Down
8 changes: 7 additions & 1 deletion qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
:toctree: ../stubs/
synth_permutation_depth_lnn_kms
synth_permutation_basic
synth_permutation_acg
Clifford Synthesis
==================
Expand Down Expand Up @@ -86,8 +88,12 @@
QDrift,
)

from .permutation import (
synth_permutation_depth_lnn_kms,
synth_permutation_basic,
synth_permutation_acg,
)
from .linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms
from .permutation import synth_permutation_depth_lnn_kms
from .clifford import (
synth_clifford_full,
synth_clifford_ag,
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 @@ -14,3 +14,4 @@


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

"""Synthesis algorithm for Permutation gates for full-connectivity."""

from qiskit.circuit.quantumcircuit import QuantumCircuit
from .permutation_utils import (
_get_ordered_swap,
_inverse_pattern,
_pattern_to_cycles,
_decompose_cycles,
)


def synth_permutation_basic(pattern):
"""Synthesize a permutation circuit for a fully-connected
architecture using sorting.
More precisely, if the input permutation is a cycle of length ``m``,
then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``);
if the input permutation consists of several disjoint cycles, then each cycle
is essentially treated independently.
Args:
pattern (Union[list[int], np.ndarray]): permutation pattern, describing
which qubits occupy the positions 0, 1, 2, etc. after applying the
permutation. That is, ``pattern[k] = m`` when the permutation maps
qubit ``m`` to position ``k``. As an example, the pattern ``[2, 4, 3, 0, 1]``
means that qubit ``2`` goes to position ``0``, qubit ``4`` goes to
position ``1``, etc.
Returns:
QuantumCircuit: the synthesized quantum circuit.
"""
# This is the very original Qiskit algorithm for synthesizing permutations.

num_qubits = len(pattern)
qc = QuantumCircuit(num_qubits)

swaps = _get_ordered_swap(pattern)

for swap in swaps:
qc.swap(swap[0], swap[1])

return qc


def synth_permutation_acg(pattern):
"""Synthesize a permutation circuit for a fully-connected
architecture using the Alon, Chung, Graham method.
This produces a quantum circuit of depth 2 (measured in the number of SWAPs).
This implementation is based on the Theorem 2 in the paper
"Routing Permutations on Graphs Via Matchings" (1993),
available at https://www.cs.tau.ac.il/~nogaa/PDFS/r.pdf.
Args:
pattern (Union[list[int], np.ndarray]): permutation pattern, describing
which qubits occupy the positions 0, 1, 2, etc. after applying the
permutation. That is, ``pattern[k] = m`` when the permutation maps
qubit ``m`` to position ``k``. As an example, the pattern ``[2, 4, 3, 0, 1]``
means that qubit ``2`` goes to position ``0``, qubit ``4`` goes to
position ``1``, etc.
Returns:
QuantumCircuit: the synthesized quantum circuit.
"""

num_qubits = len(pattern)
qc = QuantumCircuit(num_qubits)

# invert pattern (Qiskit notation is opposite)
cur_pattern = _inverse_pattern(pattern)
cycles = _pattern_to_cycles(cur_pattern)
swaps = _decompose_cycles(cycles)

for swap in swaps:
qc.swap(swap[0], swap[1])

return qc
29 changes: 29 additions & 0 deletions qiskit/synthesis/permutation/permutation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,32 @@ def _inverse_pattern(pattern):
"""Finds inverse of a permutation pattern."""
b_map = {pos: idx for idx, pos in enumerate(pattern)}
return [b_map[pos] for pos in range(len(pattern))]


def _pattern_to_cycles(pattern):
"""Given a permutation pattern, creates its disjoint cycle decomposition."""
nq = len(pattern)
explored = [False] * nq
cycles = []
for i in pattern:
cycle = []
while not explored[i]:
cycle.append(i)
explored[i] = True
i = pattern[i]
if len(cycle) >= 2:
cycles.append(cycle)
return cycles


def _decompose_cycles(cycles):
"""Given a disjoint cycle decomposition, decomposes every cycle into a SWAP
circuit of depth 2."""
swap_list = []
for cycle in cycles:
m = len(cycle)
for i in range((m - 1) // 2):
swap_list.append((cycle[i - 1], cycle[m - 3 - i]))
for i in range(m // 2):
swap_list.append((cycle[i - 1], cycle[m - 2 - i]))
return swap_list
Loading

0 comments on commit 179c29c

Please sign in to comment.