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

Oxidize synth_permutation_acg #12543

Merged
merged 33 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
39bab3e
Move utility functions _inverse_pattern and _get_ordered_swap to Rust
jpacold May 2, 2024
d278d3d
Merge branch 'Qiskit:main' into main
jpacold May 2, 2024
64a8e2f
fix formatting and pylint issues
jpacold May 2, 2024
e38b84e
Merge branch 'Qiskit:main' into main
jpacold May 2, 2024
7135c3a
Merge branch 'main' into main
jpacold May 7, 2024
9dc3571
Changed input type to `PyArrayLike1<i64, AllowTypeChange>`
jpacold May 7, 2024
71ebb9c
Merge branch 'main' into main
jpacold May 16, 2024
8b5e6b3
Refactor `permutation.rs`, clean up imports, fix coverage error
jpacold May 16, 2024
3fdde4f
Merge branch 'main' into main
raynelfss May 29, 2024
d0e347c
fix docstring for `_inverse_pattern`
jpacold May 30, 2024
6e3bc2c
fix docstring for `_get_ordered_swap`
jpacold May 30, 2024
6c67959
remove pymodule nesting
jpacold May 31, 2024
3d0b012
Merge branch 'main' of github.com:jpacold/qiskit into permutation-utils
Cryoris Jun 6, 2024
b0992b3
remove explicit `AllowTypeChange`
jpacold Jun 6, 2024
3647b16
Move input validation out of `_inverse_pattern` and `_get_ordered_swap`
jpacold Jun 7, 2024
d983a30
oxidization attempt no. 1
Cryoris Jun 10, 2024
5069588
Merge branch 'main' of github.com:jpacold/qiskit into permutation-utils
Cryoris Jun 10, 2024
3b7bb1c
working version!
Cryoris Jun 10, 2024
4008814
move more into rust & fix clones
Cryoris Jun 10, 2024
836a12c
layouting & comments
Cryoris Jun 11, 2024
b4c385c
Merge branch 'main' into permutation-utils
Cryoris Jun 11, 2024
9afeda2
dangling comment
Cryoris Jun 11, 2024
670d828
Merge branch 'main' into permutation-utils
Cryoris Jun 13, 2024
63215c5
circuit construction in rust
Cryoris Jun 13, 2024
4beba74
remove dangling code
Cryoris Jun 17, 2024
a8e5d3e
more lint
Cryoris Jun 17, 2024
2c0e33d
add reno
Cryoris Jun 19, 2024
f72d7d3
Merge branch 'main' into permutation-utils
Cryoris Jun 19, 2024
7cfa7f5
drop redundant Ok(expect())
Cryoris Jun 21, 2024
2bdaa79
Merge branch 'main' into permutation-utils
Cryoris Jul 1, 2024
0743d23
Implements Shelly's suggestions
Cryoris Jul 1, 2024
e4b95f8
Merge branch 'main' into permutation-utils
Cryoris Jul 2, 2024
46a37aa
simplify code a little
Cryoris Jul 2, 2024
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
23 changes: 23 additions & 0 deletions crates/accelerate/src/synthesis/permutation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,33 @@ pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1<i64>) -> PyRes
)
}

#[pyfunction]
#[pyo3(signature = (pattern))]
fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1<i64>) -> PyResult<CircuitData> {
let view = pattern.as_array();
let num_qubits = view.len();
let cycles = utils::pattern_to_cycles(&view, &true);
let swaps = utils::decompose_cycles(&cycles);

CircuitData::from_standard_gates(
py,
num_qubits as u32,
swaps.iter().map(|(i, j)| {
(
StandardGate::SwapGate,
smallvec![],
smallvec![Qubit(*i as u32), Qubit(*j as u32)],
)
}),
Param::Float(0.0),
)
}

#[pymodule]
pub fn permutation(m: &Bound<PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?;
m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?;
m.add_function(wrap_pyfunction!(_synth_permutation_basic, m)?)?;
m.add_function(wrap_pyfunction!(_synth_permutation_acg, m)?)?;
Ok(())
}
71 changes: 68 additions & 3 deletions crates/accelerate/src/synthesis/permutation/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,19 @@ pub fn invert(pattern: &ArrayView1<i64>) -> Array1<usize> {
/// 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.
pub fn get_ordered_swap(pattern: &ArrayView1<i64>) -> Vec<(i64, i64)> {
pub fn get_ordered_swap(pattern: &ArrayView1<i64>) -> Vec<(usize, usize)> {
let mut permutation: Vec<usize> = pattern.iter().map(|&x| x as usize).collect();
let mut index_map = invert(pattern);

let n = permutation.len();
let mut swaps: Vec<(i64, i64)> = Vec::with_capacity(n);
let mut swaps: Vec<(usize, usize)> = Vec::with_capacity(n);
for ii in 0..n {
let val = permutation[ii];
if val == ii {
continue;
}
let jj = index_map[ii];
swaps.push((ii as i64, jj as i64));
swaps.push((ii, jj));
(permutation[ii], permutation[jj]) = (permutation[jj], permutation[ii]);
index_map[val] = jj;
index_map[ii] = ii;
Expand All @@ -84,3 +84,68 @@ pub fn get_ordered_swap(pattern: &ArrayView1<i64>) -> Vec<(i64, i64)> {
swaps[..].reverse();
swaps
}

/// Explore cycles in a permutation pattern. This is probably best explained in an
/// example: let a pattern be [1, 2, 3, 0, 4, 6, 5], then it contains the two
/// cycles [1, 2, 3, 0] and [6, 5]. The index [4] does not perform a permutation and does
/// therefore not create a cycle.
pub fn pattern_to_cycles(pattern: &ArrayView1<i64>, invert_order: &bool) -> Vec<Vec<usize>> {
// vector keeping track of which elements in the permutation pattern have been visited
let mut explored: Vec<bool> = vec![false; pattern.len()];

// vector to store the cycles
let mut cycles: Vec<Vec<usize>> = Vec::new();

// cast the input pattern in terms of integers to usize, such that it can be used as index
// also invert the bit ordering if ``invert_order`` is true
let permutation: Array1<usize> = if *invert_order {
invert(pattern) // implies cast to usize
} else {
pattern.mapv(|x| x as usize)
};

for mut i in permutation.clone() {
let mut cycle: Vec<usize> = Vec::new();

// follow the cycle until we reached an entry we saw before
while !explored[i] {
cycle.push(i);
explored[i] = true;
i = permutation[i];
}
// cycles must have more than 1 element
if cycle.len() > 1 {
cycles.push(cycle);
}
}

cycles
}

/// Given a disjoint cycle decomposition of a permutation pattern (see the function
/// ``pattern_to_cycles``), decomposes every cycle into a series of SWAPs to implement it.
/// In combination with ``pattern_to_cycle``, this function allows to implement a
/// full permutation pattern by applying SWAP gates on the returned index-pairs.
pub fn decompose_cycles(cycles: &Vec<Vec<usize>>) -> Vec<(usize, usize)> {
let mut swaps: Vec<(usize, usize)> = Vec::new();

for cycle in cycles {
let length = cycle.len();

if length > 2 {
// handle first element separately, which accesses the last element
swaps.push((cycle[length - 1], cycle[length - 3]));
for i in 1..(length - 1) / 2 {
swaps.push((cycle[i - 1], cycle[length - 3 - i]));
}
}

// no size check needed, cycles always have at least 2 elements
swaps.push((cycle[length - 1], cycle[length - 2]));
for i in 1..length / 2 {
swaps.push((cycle[i - 1], cycle[length - 2 - i]));
}
}

swaps
}
22 changes: 4 additions & 18 deletions qiskit/synthesis/permutation/permutation_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@

import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit._accelerate.synthesis.permutation import _synth_permutation_basic
from .permutation_utils import (
_inverse_pattern,
_pattern_to_cycles,
_decompose_cycles,
from qiskit._accelerate.synthesis.permutation import (
_synth_permutation_basic,
_synth_permutation_acg,
)


Expand Down Expand Up @@ -77,16 +75,4 @@ def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircui
*Routing Permutations on Graphs Via Matchings.*,
`(Full paper) <https://www.cs.tau.ac.il/~nogaa/PDFS/r.pdf>`_
"""

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
return QuantumCircuit._from_circuit_data(_synth_permutation_acg(pattern))
34 changes: 1 addition & 33 deletions qiskit/synthesis/permutation/permutation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,4 @@
"""Utility functions for handling permutations."""

# pylint: disable=unused-import
from qiskit._accelerate.synthesis.permutation import (
_inverse_pattern,
_validate_permutation,
)


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
from qiskit._accelerate.synthesis.permutation import _inverse_pattern, _validate_permutation
5 changes: 5 additions & 0 deletions releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
upgrade_synthesis:
- |
Port :func:`.synth_permutation_acg`, used to synthesize qubit permutations, to Rust.
This produces an approximate 3x performance improvement on 1000 qubit circuits.
Loading