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 48 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
76 changes: 62 additions & 14 deletions qiskit/circuit/library/generalized_gates/permutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@

import numpy as np

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


class Permutation(QuantumCircuit):
"""An n_qubit circuit that permutes qubits."""
class Permutation(Gate):
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit concerned about making this change from a backwards compatibility PoV in a single release. While I agree with this being a Gate subclass instead of a QuantumCircuit is where we want to be I'm worried about any users of this class today that may be using it with a circuit as is. For example the docs here show a good example of this with Permutation(...).draw(), but there are others I can think of the top of my head would be something like: Permutation(...).compose(sub_circuit) or Permutation.measure_all() all of which were previously completely valid which would be broken by this change.

I'm thinking we need to figure out a better way to manage this migration which leaves the QuantumCircuit based Permutation class in place as is for now but establishes a new Gate based class as a standalone thing (like PermutationGate or something) in this PR and maybe update this QuantumCircuit based one to just wrap the new Gate class. Then in the next release we can deprecate the QuantumCircuit version and tell people to use the gate directly.

Copy link
Contributor Author

@alexanderivrii alexanderivrii Jan 18, 2023

Choose a reason for hiding this comment

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

Thanks, this is a very good point. I keep relying on qiskit-terra tests to make sure that nothing gets broken, yet I keep forgetting that there is also a lot of other code outside of qiskit-terra. The example with Permutation.measure_all() clearly shows why we need to keep the old functionality. It should be quite straightforward for me to keep the old implementation and to call the new implementation differently, in this way this development no longer changes any existing behavior, it only adds new things.

I have already started doing changes, but here are a couple of preliminary thoughts. The name PermutationGate for the new class is good. For objects of type PermutationGate we can still have op.name = "permutation", since there is no danger to confuse permutation circuits and permutation gates (and in any case the name assigned to a permutation circuit is never "pemutation" but rather something like "permutation_[2,4,3,0,1]"). In particular, we can keep using names like permutation.kms in entry_points to specify permutation synthesis algorithms for PermutationGate objects.

The suggested deprecation flow also allows to avoid passing num_qubits and seed to the initializer of the PermutationGate, making the code a bit cleaner. There is a function np.random.permutation that returns a random permutation pattern, from which a PermutationGate can be constructed.

"""An gate that permutes qubits."""

def __init__(
self,
num_qubits: int,
pattern: Optional[List[int]] = None,
seed: Optional[int] = None,
) -> None:
"""Return an n_qubit permutation circuit implemented using SWAPs.
"""Return a permutation gate.

Args:
num_qubits: circuit width.
Expand All @@ -50,20 +50,26 @@ def __init__(
.. jupyter-execute::
:hide-code:

from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.library import Permutation
import qiskit.tools.jupyter
A = [2,4,3,0,1]
circuit = Permutation(5, A)
permutation = Permutation(5, A)
circuit = QuantumCircuit(5)
circuit.append(permutation, [0, 1, 2, 3, 4])
circuit.draw('mpl')

Expanded Circuit:
.. jupyter-execute::
:hide-code:

from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.library import Permutation
import qiskit.tools.jupyter
A = [2,4,3,0,1]
circuit = Permutation(5, A)
permutation = Permutation(5, A)
circuit = QuantumCircuit(5)
circuit.append(permutation, [0, 1, 2, 3, 4])
%circuit_library_info circuit.decompose()
"""
if pattern is not None:
Expand All @@ -77,17 +83,59 @@ def __init__(
pattern = np.arange(num_qubits)
rng.shuffle(pattern)

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

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

super().__init__(num_qubits, name=name)
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 _get_ordered_swap
from qiskit.synthesis.permutation.permutation_utils import _inverse_pattern

return Permutation(self.num_qubits, 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

for i, j in _get_ordered_swap(pattern):
circuit.swap(i, j)
# 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 + "}"
)

all_qubits = self.qubits
self.append(circuit.to_gate(), all_qubits)
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 @@ -1691,20 +1691,30 @@ def qasm(
operation = operation.copy(name=_qasm_escape_gate_name(operation.name))

# 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 @@ -4073,6 +4083,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.Permutation`.

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 Permutation

return self.append(Permutation(len(pattern), pattern), qubits, [])

def _push_scope(
self,
qubits: Iterable[Qubit] = (),
Expand Down
2 changes: 2 additions & 0 deletions qiskit/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version,
params = [len(qargs)]
elif gate_name in {"BreakLoopOp", "ContinueLoopOp"}:
params = [len(qargs), len(cargs)]
elif gate_name == "Permutation":
params = [len(qargs), params[0]]
gate = gate_class(*params)
gate.condition = condition_tuple
if instruction.label_size > 0:
Expand Down
8 changes: 7 additions & 1 deletion qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
:toctree: ../stubs/

synth_permutation_depth_lnn_kms
synth_permutation_basic
synth_permutation_acg

Clifford Synthesis
==================
Expand Down Expand Up @@ -79,8 +81,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