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

High-level-synthesis for permutations #9157

Merged
merged 61 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
8475b92
Adding permutation synthesis algorithm for LNN
alexanderivrii Nov 6, 2022
54c915f
release notes
alexanderivrii Nov 6, 2022
6a99690
Checking that the synthesized permutation adheres to the LNN connecti…
alexanderivrii Nov 7, 2022
7d65299
Adding tests for 15 qubits
alexanderivrii Nov 7, 2022
5665623
Changing Permutation to be a Gate rather than QuantumCircuit
alexanderivrii Nov 8, 2022
3b6bf99
Adding the property pattern to Permutation class
alexanderivrii Nov 8, 2022
7b21ffe
fixing assert
alexanderivrii Nov 9, 2022
5872607
improving description message for _get_ordered_swap
alexanderivrii Nov 9, 2022
3b67f88
Merge branch 'main' into permutations-over-lnn
alexanderivrii Nov 12, 2022
fe4c5ac
applying suggestions from code review
alexanderivrii Nov 12, 2022
c908692
Merge branch 'permutations-over-lnn' of github.com:alexanderivrii/qis…
alexanderivrii Nov 12, 2022
64b41bd
minor
alexanderivrii Nov 12, 2022
63d4aba
attempt to fix docstring
alexanderivrii Nov 12, 2022
b80ec53
Another attempt to fix docsting
alexanderivrii Nov 12, 2022
542d9fb
another attempt to fix docstring
alexanderivrii Nov 12, 2022
d0d61f4
temporarily simplifying docstring to see if this passes docs build
alexanderivrii Nov 13, 2022
e85dbf2
adding blank line
alexanderivrii Nov 13, 2022
500f093
another attempt
alexanderivrii Nov 13, 2022
40e494c
Restoring docstring
alexanderivrii Nov 14, 2022
f9391ee
removing extra line
alexanderivrii Nov 14, 2022
ea5f191
Merge branch 'main' into permutations-over-lnn
alexanderivrii Nov 16, 2022
00b2345
Merge branch 'main' into permutations-hls
alexanderivrii Nov 16, 2022
235b9bb
Merge branch 'permutations-over-lnn' into permutations-hls
alexanderivrii Nov 16, 2022
8baa0a8
adding __array__ method for permutation + tests
alexanderivrii Nov 16, 2022
4c63a77
HLS permutation plugin based on the original synthesis algorithm for …
alexanderivrii Nov 16, 2022
15f1228
speeding up _get_ordered_swap based on review comments
alexanderivrii Nov 16, 2022
9e9d796
Merge branch 'permutations-over-lnn' into permutations-hls
alexanderivrii Nov 17, 2022
c273097
Adding depth-2 synthesis algorithm for permutations for all-to-all co…
alexanderivrii Nov 17, 2022
9be458f
release notes
alexanderivrii Nov 17, 2022
58148dc
Adding example to release notes
alexanderivrii Nov 17, 2022
a609575
Merge branch 'main' into permutations-hls
alexanderivrii Nov 20, 2022
297cc75
Update documentation of Permutation
alexanderivrii Nov 20, 2022
a37c98c
add missing import
alexanderivrii Nov 20, 2022
85bdfa5
drawing decomposed circuit with permutations
alexanderivrii Nov 20, 2022
55a0ab5
forgot parenthesis
alexanderivrii Nov 20, 2022
8b80e02
Merge branch 'main' into permutations-hls
alexanderivrii Dec 8, 2022
ec71978
restoring qasm for circuits containing Permutations
alexanderivrii Dec 8, 2022
6d6c5f3
Adding permutation method to QuantumCircuit
alexanderivrii Dec 8, 2022
6559510
Adding test for quantum circuit with permutations
alexanderivrii Dec 8, 2022
a686c5b
pylint
alexanderivrii Dec 8, 2022
e139bf2
adding inverse() method to permutations
alexanderivrii Dec 11, 2022
3d97306
qpy support for permutations
alexanderivrii Dec 11, 2022
2cfa53c
tests for quantum circuits with permutations
alexanderivrii Dec 11, 2022
4584353
Merge branch 'main' into permutations-hls
alexanderivrii Dec 20, 2022
bdf5966
Merge branch 'main' into permutations-hls
alexanderivrii Dec 21, 2022
8ff8fb1
Merge branch 'main' into permutations-hls
alexanderivrii Dec 27, 2022
42fdd0a
checking depth bound on the ACG method
alexanderivrii Dec 27, 2022
e826085
Adding tests for new Permutation functionality
alexanderivrii Dec 27, 2022
1a02cd2
Merge branch 'main' into permutations-hls
alexanderivrii Jan 9, 2023
ced6d6a
black
alexanderivrii Jan 9, 2023
d07dd3f
Following review, keeping the old Permutation quantum circuit for bac…
alexanderivrii Jan 18, 2023
fc63af6
Merge branch 'main' into permutations-hls
alexanderivrii Jan 18, 2023
f2ecb31
additional fixes
alexanderivrii Jan 18, 2023
a408fef
updating release notes
alexanderivrii Jan 18, 2023
38743ec
docs fix
alexanderivrii Jan 18, 2023
e1e9f05
Removing permutation method from QuantumCircuit
alexanderivrii Jan 18, 2023
e577372
Adding QPY test for circuits with permutation gates
alexanderivrii Jan 18, 2023
4eca538
Update qiskit/circuit/quantumcircuit.py
alexanderivrii Jan 19, 2023
79c7a73
Set default qasm name override to None
mtreinish Jan 19, 2023
97c1f78
Merge branch 'main' into permutations-hls
mtreinish Jan 19, 2023
794d31b
Merge branch 'main' into permutations-hls
mergify[bot] Jan 19, 2023
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/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):
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
"""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):
ShellyGarion marked this conversation as resolved.
Show resolved Hide resolved
"""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):
ShellyGarion marked this conversation as resolved.
Show resolved Hide resolved
"""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)
53 changes: 39 additions & 14 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1698,20 +1698,30 @@ 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:
# If the operation has a unique qasm name, we set the name of the operation
# to this qasm name. This is now the case for the permutation gate, for which
# the unique qasm name is based on the permutation pattern.
op_qasm_name = getattr(operation, "_qasm_name", None)
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
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 Expand Up @@ -4077,6 +4087,21 @@ def pauli(

return self.append(PauliGate(pauli_string), qubits, [])

def permutation(self, pattern: List[int], qubits: Sequence[QubitSpecifier]) -> InstructionSet:
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
r"""Apply :class:`~qiskit.circuit.library.PermutationGate`.

Args:
pattern: The permutation pattern.
qubits: The qubit(s) to apply the gate to.

Returns:
A handle to the instructions created.
"""
# pylint: disable=cyclic-import
from .library.generalized_gates.permutation import PermutationGate

return self.append(PermutationGate(pattern), qubits, [])

def _push_scope(
self,
qubits: Iterable[Qubit] = (),
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,
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
synth_permutation_acg,
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
)
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