From 39bab3eb9586d3682052ffda271b7ace72c6ad0c Mon Sep 17 00:00:00 2001 From: jpacold Date: Thu, 2 May 2024 01:41:58 -0600 Subject: [PATCH 01/21] Move utility functions _inverse_pattern and _get_ordered_swap to Rust --- crates/accelerate/src/lib.rs | 1 + crates/accelerate/src/permutation.rs | 112 ++++++++++++++++++ crates/pyext/src/lib.rs | 4 +- qiskit/__init__.py | 2 + .../permutation/permutation_utils.py | 35 +----- .../synthesis/test_permutation_synthesis.py | 45 ++++++- 6 files changed, 165 insertions(+), 34 deletions(-) create mode 100644 crates/accelerate/src/permutation.rs diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 0af8ea6a0fce..3924c1de4092 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -23,6 +23,7 @@ pub mod isometry; pub mod nlayout; pub mod optimize_1q_gates; pub mod pauli_exp_val; +pub mod permutation; pub mod results; pub mod sabre; pub mod sampled_exp_val; diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs new file mode 100644 index 000000000000..51584409c938 --- /dev/null +++ b/crates/accelerate/src/permutation.rs @@ -0,0 +1,112 @@ +// 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. + +use pyo3::prelude::*; +use pyo3::wrap_pymodule; +use std::vec::Vec; +use pyo3::PyErr; +use pyo3::exceptions::PyValueError; + +fn validate_permutation(pattern: &[i64]) -> Result<(), PyErr> { + let n = pattern.len(); + let mut seen : Vec = vec![false; n]; + + for &x in pattern { + if x < 0 { + return Err( + PyValueError::new_err( + "Invalid permutation: input contains a negative number." + ) + ); + } + + if x as usize >= n { + return Err( + PyValueError::new_err( + format!("Invalid permutation: input has length {} and contains {}.", n, x) + ) + ); + } + + if seen[x as usize] { + return Err(PyValueError::new_err( + format!("Invalid permutation: input contains duplicate value {}", x) + ) + ); + } + seen[x as usize] = true; + } + + Ok(()) +} + +fn invert(pattern: &[i64]) -> Vec { + let mut inverse : Vec = vec![0; pattern.len()]; + pattern.iter().enumerate().for_each(|(ii, &jj)| { inverse[jj as usize] = ii; } ); + inverse +} + +/// Finds inverse of a permutation pattern. +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _inverse_pattern(py: Python, pattern: Vec) -> PyResult { + validate_permutation(&pattern)?; + Ok(invert(&pattern).to_object(py)) +} + +/// Sorts the input permutation by iterating through the permutation list +/// and putting each element to its correct position via a SWAP (if it's not +/// at the correct position already). If ``n`` is the length of the input +/// permutation, this requires at most ``n`` SWAPs. +/// +/// 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. +#[pyfunction] +#[pyo3(signature = (permutation_in))] +pub fn _get_ordered_swap(py: Python, permutation_in: Vec) -> PyResult { + validate_permutation(&permutation_in)?; + + let mut permutation: Vec = permutation_in.iter().map(|&x| x as usize).collect(); + let mut index_map = invert(&permutation_in); + + let s: usize = permutation_in.len(); + let mut swaps : Vec<(i64, i64)> = Vec::with_capacity(s); + for ii in 0..s { + let val = permutation[ii]; + if val == ii { + continue; + } + let jj = index_map[ii]; + swaps.push((ii as i64, jj as i64)); + (permutation[ii], permutation[jj]) = (permutation[jj], permutation[ii]); + index_map[val] = jj; + index_map[ii] = ii; + } + + swaps[..].reverse(); + Ok(swaps.to_object(py)) +} + +#[pymodule] +pub fn permutation_utils(m: &Bound) -> PyResult<()> { + m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; + m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; + Ok(()) +} + +#[pymodule] +pub fn permutation(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pymodule!(permutation_utils))?; + Ok(()) +} diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index a21b1307a88f..257f7a7dd5d5 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -17,7 +17,8 @@ use qiskit_accelerate::{ convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, - pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, + pauli_exp_val::pauli_expval, permutation::permutation, + results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap, two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils, vf2_layout::vf2_layout, @@ -36,6 +37,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(nlayout))?; m.add_wrapped(wrap_pymodule!(optimize_1q_gates))?; m.add_wrapped(wrap_pymodule!(pauli_expval))?; + m.add_wrapped(wrap_pymodule!(permutation))?; m.add_wrapped(wrap_pymodule!(results))?; m.add_wrapped(wrap_pymodule!(sabre))?; m.add_wrapped(wrap_pymodule!(sampled_exp_val))?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index e4fbc1729e53..97a49c0d4bb1 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -82,6 +82,8 @@ sys.modules["qiskit._accelerate.stochastic_swap"] = qiskit._accelerate.stochastic_swap sys.modules["qiskit._accelerate.two_qubit_decompose"] = qiskit._accelerate.two_qubit_decompose sys.modules["qiskit._accelerate.vf2_layout"] = qiskit._accelerate.vf2_layout +sys.modules["qiskit._accelerate.permutation"] = qiskit._accelerate.permutation +sys.modules["qiskit._accelerate.permutation.permutation_utils"] = qiskit._accelerate.permutation.permutation_utils from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index 6c6d950dc383..3ab31907a694 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -12,37 +12,10 @@ """Utility functions for handling permutations.""" - -def _get_ordered_swap(permutation_in): - """Sorts the input permutation by iterating through the permutation list - and putting each element to its correct position via a SWAP (if it's not - at the correct position already). If ``n`` is the length of the input - permutation, this requires at most ``n`` SWAPs. - - 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. - """ - permutation = list(permutation_in[:]) - swap_list = [] - index_map = _inverse_pattern(permutation_in) - for i, val in enumerate(permutation): - if val != i: - j = index_map[i] - swap_list.append((i, j)) - permutation[i], permutation[j] = permutation[j], permutation[i] - index_map[val] = j - index_map[i] = i - swap_list.reverse() - return swap_list - - -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))] - +from qiskit._accelerate.permutation.permutation_utils import( + _get_ordered_swap, + _inverse_pattern +) def _pattern_to_cycles(pattern): """Given a permutation pattern, creates its disjoint cycle decomposition.""" diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py index 5c4317ed58a3..f9883e28e450 100644 --- a/test/python/synthesis/test_permutation_synthesis.py +++ b/test/python/synthesis/test_permutation_synthesis.py @@ -25,7 +25,10 @@ synth_permutation_basic, synth_permutation_reverse_lnn_kms, ) -from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap +from qiskit.synthesis.permutation.permutation_utils import ( + _inverse_pattern, + _get_ordered_swap +) from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -33,9 +36,19 @@ class TestPermutationSynthesis(QiskitTestCase): """Test the permutation synthesis functions.""" + @data(4, 5, 10, 15, 20) + def test_inverse_pattern(self, width): + """Test _inverse_pattern function produces correct index map.""" + np.random.seed(1) + for _ in range(5): + pattern = np.random.permutation(width) + inverse = _inverse_pattern(pattern) + for ii, jj in enumerate(pattern): + self.assertTrue(inverse[jj] == ii) + @data(4, 5, 10, 15, 20) def test_get_ordered_swap(self, width): - """Test get_ordered_swap function produces correct swap list.""" + """Test _get_ordered_swap function produces correct swap list.""" np.random.seed(1) for _ in range(5): pattern = np.random.permutation(width) @@ -46,6 +59,34 @@ def test_get_ordered_swap(self, width): self.assertTrue(np.array_equal(pattern, output)) self.assertLess(len(swap_list), width) + @data(10, 20) + def test_invalid_permutations(self, width): + """Check that synth_permutation_basic raises exceptions when the + input is not a permutation.""" + np.random.seed(1) + for _ in range(5): + pattern = np.random.permutation(width) + + pattern_out_of_range = np.copy(pattern) + pattern_out_of_range[0] = width + with self.assertRaises(ValueError) as exc: + _ = synth_permutation_basic(pattern_out_of_range) + self.assertIn("input contains a negative number", str(exc.exception)) + + pattern_out_of_range = np.copy(pattern) + pattern_out_of_range[0] = width + with self.assertRaises(ValueError) as exc: + _ = synth_permutation_basic(pattern_out_of_range) + self.assertIn("input has length {} and contains {}".format(width, width), + str(exc.exception)) + + pattern_duplicate = np.copy(pattern) + pattern_duplicate[-1] = pattern[0] + with self.assertRaises(ValueError) as exc: + _ = synth_permutation_basic(pattern_duplicate) + self.assertIn("input contains duplicate value", str(exc.exception)) + + @data(4, 5, 10, 15, 20) def test_synth_permutation_basic(self, width): """Test synth_permutation_basic function produces the correct From 64a8e2fe3f3f94e0a88daf06b394e1aa58204472 Mon Sep 17 00:00:00 2001 From: jpacold Date: Thu, 2 May 2024 08:45:41 -0600 Subject: [PATCH 02/21] fix formatting and pylint issues --- crates/accelerate/src/permutation.rs | 55 ++++++++----------- crates/pyext/src/lib.rs | 9 ++- qiskit/__init__.py | 4 +- .../permutation/permutation_utils.py | 25 +++++++-- .../synthesis/test_permutation_synthesis.py | 15 ++--- 5 files changed, 56 insertions(+), 52 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 51584409c938..f1267932da20 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -10,38 +10,35 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::wrap_pymodule; -use std::vec::Vec; use pyo3::PyErr; -use pyo3::exceptions::PyValueError; +use std::vec::Vec; fn validate_permutation(pattern: &[i64]) -> Result<(), PyErr> { let n = pattern.len(); - let mut seen : Vec = vec![false; n]; - + let mut seen: Vec = vec![false; n]; + for &x in pattern { if x < 0 { - return Err( - PyValueError::new_err( - "Invalid permutation: input contains a negative number." - ) - ); + return Err(PyValueError::new_err( + "Invalid permutation: input contains a negative number.", + )); } - + if x as usize >= n { - return Err( - PyValueError::new_err( - format!("Invalid permutation: input has length {} and contains {}.", n, x) - ) - ); + return Err(PyValueError::new_err(format!( + "Invalid permutation: input has length {} and contains {}.", + n, x + ))); } if seen[x as usize] { - return Err(PyValueError::new_err( - format!("Invalid permutation: input contains duplicate value {}", x) - ) - ); + return Err(PyValueError::new_err(format!( + "Invalid permutation: input contains {} more than once.", + x + ))); } seen[x as usize] = true; } @@ -50,12 +47,13 @@ fn validate_permutation(pattern: &[i64]) -> Result<(), PyErr> { } fn invert(pattern: &[i64]) -> Vec { - let mut inverse : Vec = vec![0; pattern.len()]; - pattern.iter().enumerate().for_each(|(ii, &jj)| { inverse[jj as usize] = ii; } ); + let mut inverse: Vec = vec![0; pattern.len()]; + pattern.iter().enumerate().for_each(|(ii, &jj)| { + inverse[jj as usize] = ii; + }); inverse } -/// Finds inverse of a permutation pattern. #[pyfunction] #[pyo3(signature = (pattern))] pub fn _inverse_pattern(py: Python, pattern: Vec) -> PyResult { @@ -63,15 +61,6 @@ pub fn _inverse_pattern(py: Python, pattern: Vec) -> PyResult { Ok(invert(&pattern).to_object(py)) } -/// Sorts the input permutation by iterating through the permutation list -/// and putting each element to its correct position via a SWAP (if it's not -/// at the correct position already). If ``n`` is the length of the input -/// permutation, this requires at most ``n`` SWAPs. -/// -/// 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. #[pyfunction] #[pyo3(signature = (permutation_in))] pub fn _get_ordered_swap(py: Python, permutation_in: Vec) -> PyResult { @@ -81,7 +70,7 @@ pub fn _get_ordered_swap(py: Python, permutation_in: Vec) -> PyResult = Vec::with_capacity(s); + let mut swaps: Vec<(i64, i64)> = Vec::with_capacity(s); for ii in 0..s { let val = permutation[ii]; if val == ii { @@ -93,7 +82,7 @@ pub fn _get_ordered_swap(py: Python, permutation_in: Vec) -> PyResult Date: Mon, 6 May 2024 21:53:51 -0600 Subject: [PATCH 03/21] Changed input type to `PyArrayLike1` --- crates/accelerate/src/permutation.rs | 33 +++++++++++++------ .../synthesis/test_permutation_synthesis.py | 4 ++- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index f1267932da20..baf9f4dd381c 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -10,17 +10,19 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use numpy::{AllowTypeChange, PyArrayLike1, PyReadonlyArray1}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::wrap_pymodule; use pyo3::PyErr; use std::vec::Vec; -fn validate_permutation(pattern: &[i64]) -> Result<(), PyErr> { - let n = pattern.len(); +fn validate_permutation(pattern: &PyReadonlyArray1) -> Result<(), PyErr> { + let view = pattern.as_array(); + let n = view.len(); let mut seen: Vec = vec![false; n]; - for &x in pattern { + for &x in view { if x < 0 { return Err(PyValueError::new_err( "Invalid permutation: input contains a negative number.", @@ -46,9 +48,10 @@ fn validate_permutation(pattern: &[i64]) -> Result<(), PyErr> { Ok(()) } -fn invert(pattern: &[i64]) -> Vec { - let mut inverse: Vec = vec![0; pattern.len()]; - pattern.iter().enumerate().for_each(|(ii, &jj)| { +fn invert(pattern: &PyReadonlyArray1) -> Vec { + let view = pattern.as_array(); + let mut inverse: Vec = vec![0; view.len()]; + view.iter().enumerate().for_each(|(ii, &jj)| { inverse[jj as usize] = ii; }); inverse @@ -56,20 +59,30 @@ fn invert(pattern: &[i64]) -> Vec { #[pyfunction] #[pyo3(signature = (pattern))] -pub fn _inverse_pattern(py: Python, pattern: Vec) -> PyResult { +pub fn _inverse_pattern( + py: Python, + pattern: PyArrayLike1, +) -> PyResult { validate_permutation(&pattern)?; Ok(invert(&pattern).to_object(py)) } #[pyfunction] #[pyo3(signature = (permutation_in))] -pub fn _get_ordered_swap(py: Python, permutation_in: Vec) -> PyResult { +pub fn _get_ordered_swap( + py: Python, + permutation_in: PyArrayLike1, +) -> PyResult { validate_permutation(&permutation_in)?; - let mut permutation: Vec = permutation_in.iter().map(|&x| x as usize).collect(); + let mut permutation: Vec = permutation_in + .as_array() + .iter() + .map(|&x| x as usize) + .collect(); let mut index_map = invert(&permutation_in); - let s: usize = permutation_in.len(); + let s = permutation.len(); let mut swaps: Vec<(i64, i64)> = Vec::with_capacity(s); for ii in 0..s { let val = permutation[ii]; diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py index aefb4abc24c6..8848c8317c45 100644 --- a/test/python/synthesis/test_permutation_synthesis.py +++ b/test/python/synthesis/test_permutation_synthesis.py @@ -82,7 +82,9 @@ def test_invalid_permutations(self, width): pattern_duplicate[-1] = pattern[0] with self.assertRaises(ValueError) as exc: _ = synth_permutation_basic(pattern_duplicate) - self.assertIn("input contains duplicate value", str(exc.exception)) + self.assertIn( + "input contains {} more than once".format(pattern[0]), str(exc.exception) + ) @data(4, 5, 10, 15, 20) def test_synth_permutation_basic(self, width): From 8b5e6b31096bbfe65256ae3249cde4ba2f4689d6 Mon Sep 17 00:00:00 2001 From: jpacold Date: Thu, 16 May 2024 02:28:45 -0600 Subject: [PATCH 04/21] Refactor `permutation.rs`, clean up imports, fix coverage error --- crates/accelerate/src/permutation.rs | 84 ++++++++++--------- .../permutation/permutation_utils.py | 22 +---- .../synthesis/test_permutation_synthesis.py | 2 +- 3 files changed, 49 insertions(+), 59 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index baf9f4dd381c..7de206e8bb6c 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -10,19 +10,18 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use numpy::{AllowTypeChange, PyArrayLike1, PyReadonlyArray1}; +use ndarray::{Array1, ArrayView1}; +use numpy::{AllowTypeChange, PyArrayLike1}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::wrap_pymodule; -use pyo3::PyErr; use std::vec::Vec; -fn validate_permutation(pattern: &PyReadonlyArray1) -> Result<(), PyErr> { - let view = pattern.as_array(); - let n = view.len(); +fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { + let n = pattern.len(); let mut seen: Vec = vec![false; n]; - for &x in view { + for &x in pattern { if x < 0 { return Err(PyValueError::new_err( "Invalid permutation: input contains a negative number.", @@ -42,49 +41,28 @@ fn validate_permutation(pattern: &PyReadonlyArray1) -> Result<(), PyErr> { x ))); } + seen[x as usize] = true; } Ok(()) } -fn invert(pattern: &PyReadonlyArray1) -> Vec { - let view = pattern.as_array(); - let mut inverse: Vec = vec![0; view.len()]; - view.iter().enumerate().for_each(|(ii, &jj)| { +fn invert(pattern: &ArrayView1) -> Array1 { + let mut inverse: Array1 = Array1::zeros(pattern.len()); + pattern.iter().enumerate().for_each(|(ii, &jj)| { inverse[jj as usize] = ii; }); inverse } -#[pyfunction] -#[pyo3(signature = (pattern))] -pub fn _inverse_pattern( - py: Python, - pattern: PyArrayLike1, -) -> PyResult { - validate_permutation(&pattern)?; - Ok(invert(&pattern).to_object(py)) -} - -#[pyfunction] -#[pyo3(signature = (permutation_in))] -pub fn _get_ordered_swap( - py: Python, - permutation_in: PyArrayLike1, -) -> PyResult { - validate_permutation(&permutation_in)?; +fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { + let mut permutation: Vec = pattern.iter().map(|&x| x as usize).collect(); + let mut index_map = invert(pattern); - let mut permutation: Vec = permutation_in - .as_array() - .iter() - .map(|&x| x as usize) - .collect(); - let mut index_map = invert(&permutation_in); - - let s = permutation.len(); - let mut swaps: Vec<(i64, i64)> = Vec::with_capacity(s); - for ii in 0..s { + let n = permutation.len(); + let mut swaps: Vec<(i64, i64)> = Vec::with_capacity(n); + for ii in 0..n { let val = permutation[ii]; if val == ii { continue; @@ -97,7 +75,37 @@ pub fn _get_ordered_swap( } swaps[..].reverse(); - Ok(swaps.to_object(py)) + swaps +} + +///Finds inverse of a permutation pattern. +#[pyfunction] +#[pyo3(signature = (pattern))] +fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + validate_permutation(&view)?; + let inverse_i64: Vec = invert(&view).iter().map(|&x| x as i64).collect(); + Ok(inverse_i64.to_object(py)) +} + +///Sorts the input permutation by iterating through the permutation list +///and putting each element to its correct position via a SWAP (if it's not +///at the correct position already). If ``n`` is the length of the input +///permutation, this requires at most ``n`` SWAPs. +/// +///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. +#[pyfunction] +#[pyo3(signature = (permutation_in))] +fn _get_ordered_swap( + py: Python, + permutation_in: PyArrayLike1, +) -> PyResult { + let view = permutation_in.as_array(); + validate_permutation(&view)?; + Ok(get_ordered_swap(&view).to_object(py)) } #[pymodule] diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index a6397c6722d7..dae71b42dc60 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -12,26 +12,8 @@ """Utility functions for handling permutations.""" -from qiskit._accelerate.permutation import permutation_utils as permutation_utils_rs - - -def _get_ordered_swap(permutation_in): - """Sorts the input permutation by iterating through the permutation list - and putting each element to its correct position via a SWAP (if it's not - at the correct position already). If ``n`` is the length of the input - permutation, this requires at most ``n`` SWAPs. - - 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. - """ - return permutation_utils_rs._get_ordered_swap(permutation_in) - - -def _inverse_pattern(pattern): - """Finds inverse of a permutation pattern.""" - return permutation_utils_rs._inverse_pattern(pattern) +# pylint: disable=unused-import +from qiskit._accelerate.permutation.permutation_utils import _inverse_pattern, _get_ordered_swap def _pattern_to_cycles(pattern): diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py index 8848c8317c45..c8a6cb942042 100644 --- a/test/python/synthesis/test_permutation_synthesis.py +++ b/test/python/synthesis/test_permutation_synthesis.py @@ -65,7 +65,7 @@ def test_invalid_permutations(self, width): pattern = np.random.permutation(width) pattern_out_of_range = np.copy(pattern) - pattern_out_of_range[0] = width + pattern_out_of_range[0] = -1 with self.assertRaises(ValueError) as exc: _ = synth_permutation_basic(pattern_out_of_range) self.assertIn("input contains a negative number", str(exc.exception)) From d0e347c0f02e795ba1399fac34bba92d9794033a Mon Sep 17 00:00:00 2001 From: jpacold Date: Wed, 29 May 2024 21:09:20 -0600 Subject: [PATCH 05/21] fix docstring for `_inverse_pattern` Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> --- crates/accelerate/src/permutation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 7de206e8bb6c..45572962a88f 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -78,7 +78,7 @@ fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { swaps } -///Finds inverse of a permutation pattern. +/// Finds inverse of a permutation pattern. #[pyfunction] #[pyo3(signature = (pattern))] fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { From 6e3bc2c292d697eb180f75e9943f54839c553038 Mon Sep 17 00:00:00 2001 From: jpacold Date: Wed, 29 May 2024 21:09:48 -0600 Subject: [PATCH 06/21] fix docstring for `_get_ordered_swap` Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> --- crates/accelerate/src/permutation.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 45572962a88f..458c0885f2a5 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -88,15 +88,15 @@ fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> Ok(inverse_i64.to_object(py)) } -///Sorts the input permutation by iterating through the permutation list -///and putting each element to its correct position via a SWAP (if it's not -///at the correct position already). If ``n`` is the length of the input -///permutation, this requires at most ``n`` SWAPs. +/// Sorts the input permutation by iterating through the permutation list +/// and putting each element to its correct position via a SWAP (if it's not +/// at the correct position already). If ``n`` is the length of the input +/// permutation, this requires at most ``n`` SWAPs. /// -///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. +/// 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. #[pyfunction] #[pyo3(signature = (permutation_in))] fn _get_ordered_swap( From 6c679597f1e640af009be7c04865a21b557e6b80 Mon Sep 17 00:00:00 2001 From: jpacold Date: Thu, 30 May 2024 19:50:44 -0600 Subject: [PATCH 07/21] remove pymodule nesting --- crates/accelerate/src/permutation.rs | 9 +-------- qiskit/__init__.py | 3 --- qiskit/synthesis/permutation/permutation_utils.py | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 458c0885f2a5..768a4707448d 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -14,7 +14,6 @@ use ndarray::{Array1, ArrayView1}; use numpy::{AllowTypeChange, PyArrayLike1}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; -use pyo3::wrap_pymodule; use std::vec::Vec; fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { @@ -109,14 +108,8 @@ fn _get_ordered_swap( } #[pymodule] -pub fn permutation_utils(m: &Bound) -> PyResult<()> { +pub fn permutation(m: &Bound) -> PyResult<()> { m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; Ok(()) } - -#[pymodule] -pub fn permutation(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pymodule!(permutation_utils))?; - Ok(()) -} diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 19e6602b35b6..27126de6df66 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -83,9 +83,6 @@ sys.modules["qiskit._accelerate.two_qubit_decompose"] = qiskit._accelerate.two_qubit_decompose sys.modules["qiskit._accelerate.vf2_layout"] = qiskit._accelerate.vf2_layout sys.modules["qiskit._accelerate.permutation"] = qiskit._accelerate.permutation -sys.modules["qiskit._accelerate.permutation.permutation_utils"] = ( - qiskit._accelerate.permutation.permutation_utils -) from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index dae71b42dc60..e77068de0976 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -13,7 +13,7 @@ """Utility functions for handling permutations.""" # pylint: disable=unused-import -from qiskit._accelerate.permutation.permutation_utils import _inverse_pattern, _get_ordered_swap +from qiskit._accelerate.permutation import _inverse_pattern, _get_ordered_swap def _pattern_to_cycles(pattern): From b0992b3271baf0614f1dc66e1ad3c00ffc449e82 Mon Sep 17 00:00:00 2001 From: jpacold Date: Thu, 6 Jun 2024 08:43:51 -0600 Subject: [PATCH 08/21] remove explicit `AllowTypeChange` --- crates/accelerate/src/permutation.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 768a4707448d..f80d87882312 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -11,7 +11,7 @@ // that they have been altered from the originals. use ndarray::{Array1, ArrayView1}; -use numpy::{AllowTypeChange, PyArrayLike1}; +use numpy::PyArrayLike1; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use std::vec::Vec; @@ -80,7 +80,7 @@ fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { /// Finds inverse of a permutation pattern. #[pyfunction] #[pyo3(signature = (pattern))] -fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { +fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { let view = pattern.as_array(); validate_permutation(&view)?; let inverse_i64: Vec = invert(&view).iter().map(|&x| x as i64).collect(); @@ -98,10 +98,7 @@ fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> /// is essentially treated independently. #[pyfunction] #[pyo3(signature = (permutation_in))] -fn _get_ordered_swap( - py: Python, - permutation_in: PyArrayLike1, -) -> PyResult { +fn _get_ordered_swap(py: Python, permutation_in: PyArrayLike1) -> PyResult { let view = permutation_in.as_array(); validate_permutation(&view)?; Ok(get_ordered_swap(&view).to_object(py)) From 3647b16df7eed3c9093c15ecdc1b0a26b8cb877f Mon Sep 17 00:00:00 2001 From: jpacold Date: Thu, 6 Jun 2024 22:12:13 -0600 Subject: [PATCH 09/21] Move input validation out of `_inverse_pattern` and `_get_ordered_swap` --- crates/accelerate/src/permutation.rs | 12 ++++++++++-- qiskit/synthesis/permutation/permutation_utils.py | 6 +++++- .../python/synthesis/test_permutation_synthesis.py | 14 +++++++++----- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index f80d87882312..31ba433ddd30 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -77,12 +77,20 @@ fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { swaps } +/// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. +#[pyfunction] +#[pyo3(signature = (pattern))] +fn _validate_permutation(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + validate_permutation(&view)?; + Ok(py.None()) +} + /// Finds inverse of a permutation pattern. #[pyfunction] #[pyo3(signature = (pattern))] fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { let view = pattern.as_array(); - validate_permutation(&view)?; let inverse_i64: Vec = invert(&view).iter().map(|&x| x as i64).collect(); Ok(inverse_i64.to_object(py)) } @@ -100,12 +108,12 @@ fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult) -> PyResult { let view = permutation_in.as_array(); - validate_permutation(&view)?; Ok(get_ordered_swap(&view).to_object(py)) } #[pymodule] pub fn permutation(m: &Bound) -> PyResult<()> { + m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; Ok(()) diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index e77068de0976..dbd73bfe8111 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -13,7 +13,11 @@ """Utility functions for handling permutations.""" # pylint: disable=unused-import -from qiskit._accelerate.permutation import _inverse_pattern, _get_ordered_swap +from qiskit._accelerate.permutation import ( + _inverse_pattern, + _get_ordered_swap, + _validate_permutation, +) def _pattern_to_cycles(pattern): diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py index c8a6cb942042..a879d5251f90 100644 --- a/test/python/synthesis/test_permutation_synthesis.py +++ b/test/python/synthesis/test_permutation_synthesis.py @@ -25,7 +25,11 @@ synth_permutation_basic, synth_permutation_reverse_lnn_kms, ) -from qiskit.synthesis.permutation.permutation_utils import _inverse_pattern, _get_ordered_swap +from qiskit.synthesis.permutation.permutation_utils import ( + _inverse_pattern, + _get_ordered_swap, + _validate_permutation, +) from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -58,7 +62,7 @@ def test_get_ordered_swap(self, width): @data(10, 20) def test_invalid_permutations(self, width): - """Check that synth_permutation_basic raises exceptions when the + """Check that _validate_permutation raises exceptions when the input is not a permutation.""" np.random.seed(1) for _ in range(5): @@ -67,13 +71,13 @@ def test_invalid_permutations(self, width): pattern_out_of_range = np.copy(pattern) pattern_out_of_range[0] = -1 with self.assertRaises(ValueError) as exc: - _ = synth_permutation_basic(pattern_out_of_range) + _validate_permutation(pattern_out_of_range) self.assertIn("input contains a negative number", str(exc.exception)) pattern_out_of_range = np.copy(pattern) pattern_out_of_range[0] = width with self.assertRaises(ValueError) as exc: - _ = synth_permutation_basic(pattern_out_of_range) + _validate_permutation(pattern_out_of_range) self.assertIn( "input has length {0} and contains {0}".format(width), str(exc.exception) ) @@ -81,7 +85,7 @@ def test_invalid_permutations(self, width): pattern_duplicate = np.copy(pattern) pattern_duplicate[-1] = pattern[0] with self.assertRaises(ValueError) as exc: - _ = synth_permutation_basic(pattern_duplicate) + _validate_permutation(pattern_duplicate) self.assertIn( "input contains {} more than once".format(pattern[0]), str(exc.exception) ) From d983a30f3e3a631567ef864c875054a646e31997 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 10 Jun 2024 08:27:38 +0200 Subject: [PATCH 10/21] oxidization attempt no. 1 --- crates/accelerate/src/permutation.rs | 46 ++++++++++++++++++- .../permutation/permutation_utils.py | 30 ++++++------ 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 768a4707448d..bda805c38696 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -77,12 +77,39 @@ fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { swaps } +fn pattern_to_cycles(pattern: &ArrayView1) -> Vec> { + // vector keeping track of which elements in the permutation pattern have been visited + let mut explored: Vec = vec![false; pattern.len()]; + + // vector of the discovered cycles + let mut cycles: Vec> = Vec::new(); + + // turn pattern into unsigned integer which can be used as indices + let permutation: Vec = pattern.iter().map(|&x| x as usize).collect(); + + for mut ii in permutation.clone() { + let mut cycle: Vec = Vec::new(); + + // follow the cycle until we reached an entry we saw before + while !explored[ii] { + cycle.push(ii.clone()); + explored[ii] = true; + ii = permutation[ii]; + } + // cycles must have more than 1 element + if cycle.len() > 1 { + cycles.push(cycle); + } + } + + cycles +} + /// Finds inverse of a permutation pattern. #[pyfunction] #[pyo3(signature = (pattern))] fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { let view = pattern.as_array(); - validate_permutation(&view)?; let inverse_i64: Vec = invert(&view).iter().map(|&x| x as i64).collect(); Ok(inverse_i64.to_object(py)) } @@ -103,13 +130,28 @@ fn _get_ordered_swap( permutation_in: PyArrayLike1, ) -> PyResult { let view = permutation_in.as_array(); - validate_permutation(&view)?; Ok(get_ordered_swap(&view).to_object(py)) } +/// Find cycles in a permutation pattern. +#[pyfunction] +#[pyo3(signature = (pattern))] +fn _pattern_to_cycles( + py: Python, + pattern: PyArrayLike1, +) -> PyResult { + let view = pattern.as_array(); + let cycles_i64: Vec> = pattern_to_cycles(&view) + .iter() + .map(|cycles| cycles.iter().map(|&idx| idx as i64).collect()) + .collect(); + Ok(cycles_i64.to_object(py)) +} + #[pymodule] pub fn permutation(m: &Bound) -> PyResult<()> { m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; + m.add_function(wrap_pyfunction!(_pattern_to_cycles, m)?)?; Ok(()) } diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index e77068de0976..02d2825dea6c 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -13,23 +13,23 @@ """Utility functions for handling permutations.""" # pylint: disable=unused-import -from qiskit._accelerate.permutation import _inverse_pattern, _get_ordered_swap +from qiskit._accelerate.permutation import _inverse_pattern, _get_ordered_swap, _pattern_to_cycles -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 _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): From 3b7bb1c9b42b5ddf629f81453a87153b0d63873f Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 10 Jun 2024 11:14:59 +0200 Subject: [PATCH 11/21] working version! maybe faster possible... --- crates/accelerate/src/permutation.rs | 44 +++++++++++++++---- .../synthesis/permutation/permutation_full.py | 6 +-- .../permutation/permutation_utils.py | 31 +------------ 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 6190ce915214..85dc693b8820 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -105,6 +105,31 @@ fn pattern_to_cycles(pattern: &ArrayView1) -> Vec> { cycles } +/// Given a disjoint cycle decomposition, decomposes every cycle into a SWAP +fn decompose_cycles(cycles: &Vec>) -> 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].clone(), cycle[length - 3].clone())); + for ii in 1..(length - 1) / 2 { + swaps.push((cycle[ii - 1].clone(), cycle[length - 3 - ii].clone())); + } + } + + // no check needed, cycles always have at least 2 elements + swaps.push((cycle[length - 1].clone(), cycle[length - 2].clone())); + for ii in 1..length / 2 { + swaps.push((cycle[ii - 1].clone(), cycle[length - 2 - ii].clone())); + } + } + + swaps +} + /// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. #[pyfunction] #[pyo3(signature = (pattern))] @@ -139,16 +164,19 @@ fn _get_ordered_swap(py: Python, permutation_in: PyArrayLike1) -> PyResult< Ok(get_ordered_swap(&view).to_object(py)) } -/// Find cycles in a permutation pattern. +/// Decompose a SWAP pattern into a series of SWAP gate indices to implement them. +/// For example, let the pattern be [1, 2, 3, 4, 0, 6, 5], which contains the two cycles +/// [1, 2, 3, 4, 0] and [6, 5]. These can then be implemented with the SWAPs +/// [(0, 3), (1, 2), (0, 4), (1, 3)], respectively [(6, 5)]. +/// If ``invert`` is True, reverse the indices before computing the SWAPs. #[pyfunction] #[pyo3(signature = (pattern))] -fn _pattern_to_cycles(py: Python, pattern: PyArrayLike1) -> PyResult { +fn _pattern_to_swaps(py: Python, pattern: PyArrayLike1) -> PyResult { let view = pattern.as_array(); - let cycles_i64: Vec> = pattern_to_cycles(&view) - .iter() - .map(|cycles| cycles.iter().map(|&idx| idx as i64).collect()) - .collect(); - Ok(cycles_i64.to_object(py)) + let cycles = pattern_to_cycles(&view); + let swaps = decompose_cycles(&cycles); + let swaps_i64: Vec<(i64, i64)> = swaps.iter().map(|&x| (x.0 as i64, x.1 as i64)).collect(); + Ok(swaps_i64.to_object(py)) } #[pymodule] @@ -156,6 +184,6 @@ pub fn permutation(m: &Bound) -> PyResult<()> { m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; - m.add_function(wrap_pyfunction!(_pattern_to_cycles, m)?)?; + m.add_function(wrap_pyfunction!(_pattern_to_swaps, m)?)?; Ok(()) } diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index ff014cb3a051..435d51b0aa11 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -19,8 +19,7 @@ from .permutation_utils import ( _get_ordered_swap, _inverse_pattern, - _pattern_to_cycles, - _decompose_cycles, + _pattern_to_swaps, ) @@ -93,8 +92,7 @@ def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircui # invert pattern (Qiskit notation is opposite) cur_pattern = _inverse_pattern(pattern) - cycles = _pattern_to_cycles(cur_pattern) - swaps = _decompose_cycles(cycles) + swaps = _pattern_to_swaps(cur_pattern) for swap in swaps: qc.swap(swap[0], swap[1]) diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index ec36f405b27d..04daa61c794e 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -17,34 +17,5 @@ _inverse_pattern, _get_ordered_swap, _validate_permutation, - _pattern_to_cycles, + _pattern_to_swaps, ) - - -# 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 40088142518734b460fbc874beb3be2b1a486f54 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 10 Jun 2024 12:10:37 +0200 Subject: [PATCH 12/21] move more into rust & fix clones --- crates/accelerate/src/permutation.rs | 34 ++++++++++++------- .../synthesis/permutation/permutation_full.py | 7 ++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 85dc693b8820..8c0bc6737eb8 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -10,7 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use ndarray::{Array1, ArrayView1}; +use ndarray::ArrayView1; use numpy::PyArrayLike1; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; @@ -47,8 +47,8 @@ fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { Ok(()) } -fn invert(pattern: &ArrayView1) -> Array1 { - let mut inverse: Array1 = Array1::zeros(pattern.len()); +fn invert(pattern: &ArrayView1) -> Vec { + let mut inverse: Vec = vec![0usize; pattern.len()]; pattern.iter().enumerate().for_each(|(ii, &jj)| { inverse[jj as usize] = ii; }); @@ -77,7 +77,7 @@ fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { swaps } -fn pattern_to_cycles(pattern: &ArrayView1) -> Vec> { +fn pattern_to_cycles(pattern: &ArrayView1, invert_order: &bool) -> Vec> { // vector keeping track of which elements in the permutation pattern have been visited let mut explored: Vec = vec![false; pattern.len()]; @@ -85,14 +85,18 @@ fn pattern_to_cycles(pattern: &ArrayView1) -> Vec> { let mut cycles: Vec> = Vec::new(); // turn pattern into unsigned integer which can be used as indices - let permutation: Vec = pattern.iter().map(|&x| x as usize).collect(); + let permutation: Vec = if *invert_order { + invert(pattern) + } else { + pattern.iter().map(|&x| x as usize).collect() + }; for mut ii in permutation.clone() { let mut cycle: Vec = Vec::new(); // follow the cycle until we reached an entry we saw before while !explored[ii] { - cycle.push(ii.clone()); + cycle.push(ii); explored[ii] = true; ii = permutation[ii]; } @@ -114,16 +118,16 @@ fn decompose_cycles(cycles: &Vec>) -> Vec<(usize, usize)> { if length > 2 { // handle first element separately, which accesses the last element - swaps.push((cycle[length - 1].clone(), cycle[length - 3].clone())); + swaps.push((cycle[length - 1], cycle[length - 3])); for ii in 1..(length - 1) / 2 { - swaps.push((cycle[ii - 1].clone(), cycle[length - 3 - ii].clone())); + swaps.push((cycle[ii - 1], cycle[length - 3 - ii])); } } // no check needed, cycles always have at least 2 elements - swaps.push((cycle[length - 1].clone(), cycle[length - 2].clone())); + swaps.push((cycle[length - 1], cycle[length - 2])); for ii in 1..length / 2 { - swaps.push((cycle[ii - 1].clone(), cycle[length - 2 - ii].clone())); + swaps.push((cycle[ii - 1], cycle[length - 2 - ii])); } } @@ -170,10 +174,14 @@ fn _get_ordered_swap(py: Python, permutation_in: PyArrayLike1) -> PyResult< /// [(0, 3), (1, 2), (0, 4), (1, 3)], respectively [(6, 5)]. /// If ``invert`` is True, reverse the indices before computing the SWAPs. #[pyfunction] -#[pyo3(signature = (pattern))] -fn _pattern_to_swaps(py: Python, pattern: PyArrayLike1) -> PyResult { +#[pyo3(signature = (pattern, invert_order=true))] +fn _pattern_to_swaps( + py: Python, + pattern: PyArrayLike1, + invert_order: bool, +) -> PyResult { let view = pattern.as_array(); - let cycles = pattern_to_cycles(&view); + let cycles = pattern_to_cycles(&view, &invert_order); let swaps = decompose_cycles(&cycles); let swaps_i64: Vec<(i64, i64)> = swaps.iter().map(|&x| (x.0 as i64, x.1 as i64)).collect(); Ok(swaps_i64.to_object(py)) diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index 435d51b0aa11..f263eaddc11e 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -18,7 +18,6 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from .permutation_utils import ( _get_ordered_swap, - _inverse_pattern, _pattern_to_swaps, ) @@ -90,9 +89,9 @@ def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircui num_qubits = len(pattern) qc = QuantumCircuit(num_qubits) - # invert pattern (Qiskit notation is opposite) - cur_pattern = _inverse_pattern(pattern) - swaps = _pattern_to_swaps(cur_pattern) + # Compute the swap layers from the permutation pattern. ``invert_order`` inverts the + # index order to be consistent with Qiskit notation. + swaps = _pattern_to_swaps(pattern, invert_order=True) for swap in swaps: qc.swap(swap[0], swap[1]) From 836a12ca8b5e8bbc24872bf05933a40e7f00d9bb Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 11 Jun 2024 10:20:39 +0200 Subject: [PATCH 13/21] layouting & comments --- crates/accelerate/src/permutation.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 8c0bc6737eb8..3a9ace1854c3 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -81,24 +81,25 @@ fn pattern_to_cycles(pattern: &ArrayView1, invert_order: &bool) -> Vec = vec![false; pattern.len()]; - // vector of the discovered cycles + // vector to store the cycles let mut cycles: Vec> = Vec::new(); - // turn pattern into unsigned integer which can be used as indices + // 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: Vec = if *invert_order { - invert(pattern) + invert(pattern) // implies cast to usize } else { pattern.iter().map(|&x| x as usize).collect() }; - for mut ii in permutation.clone() { + for mut i in permutation.clone() { let mut cycle: Vec = Vec::new(); // follow the cycle until we reached an entry we saw before - while !explored[ii] { - cycle.push(ii); - explored[ii] = true; - ii = permutation[ii]; + while !explored[i] { + cycle.push(i); + explored[i] = true; + i = permutation[i]; } // cycles must have more than 1 element if cycle.len() > 1 { @@ -119,15 +120,15 @@ fn decompose_cycles(cycles: &Vec>) -> Vec<(usize, usize)> { if length > 2 { // handle first element separately, which accesses the last element swaps.push((cycle[length - 1], cycle[length - 3])); - for ii in 1..(length - 1) / 2 { - swaps.push((cycle[ii - 1], cycle[length - 3 - ii])); + for i in 1..(length - 1) / 2 { + swaps.push((cycle[i - 1], cycle[length - 3 - i])); } } - // no check needed, cycles always have at least 2 elements + // no size check needed, cycles always have at least 2 elements swaps.push((cycle[length - 1], cycle[length - 2])); - for ii in 1..length / 2 { - swaps.push((cycle[ii - 1], cycle[length - 2 - ii])); + for i in 1..length / 2 { + swaps.push((cycle[i - 1], cycle[length - 2 - i])); } } From 9afeda2ef4565162d1abcb5a583ca9ba601f0e5a Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 11 Jun 2024 15:20:43 +0200 Subject: [PATCH 14/21] dangling comment --- crates/accelerate/src/permutation.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index 2c7b5d9d09ef..b50d7a8ba043 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -89,7 +89,6 @@ fn pattern_to_cycles(pattern: &ArrayView1, invert_order: &bool) -> Vec = if *invert_order { invert(pattern) // implies cast to usize } else { - // TODO change to using Array1 insted of Vec pattern.mapv(|x| x as usize) }; From 63215c5e1bf5fb99e5447def1dd7c6fe742d59cc Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 13 Jun 2024 16:16:35 +0200 Subject: [PATCH 15/21] circuit construction in rust --- crates/accelerate/src/permutation.rs | 32 +++++++++++++++++++ .../synthesis/permutation/permutation_full.py | 2 ++ 2 files changed, 34 insertions(+) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index b50d7a8ba043..e662ec4a9b9a 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -14,8 +14,13 @@ use ndarray::{Array1, ArrayView1}; use numpy::PyArrayLike1; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; +use smallvec::smallvec; use std::vec::Vec; +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::operations::{Param, StandardGate}; +use qiskit_circuit::Qubit; + fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { let n = pattern.len(); let mut seen: Vec = vec![false; n]; @@ -135,6 +140,32 @@ fn decompose_cycles(cycles: &Vec>) -> Vec<(usize, usize)> { swaps } +#[pyfunction] +#[pyo3(signature = (pattern))] +fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1) -> PyResult> { + let view = pattern.as_array(); + let num_qubits = view.len(); + let cycles = pattern_to_cycles(&view, &true); + let swaps = decompose_cycles(&cycles); + + Ok(Some( + 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), + ) + .expect("Something went wrong on Qiskit's Python side, nothing to do here!"), + )) + // let swaps_i64: Vec<(i64, i64)> = swaps.iter().map(|&x| (x.0 as i64, x.1 as i64)).collect(); +} + /// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. #[pyfunction] #[pyo3(signature = (pattern))] @@ -194,5 +225,6 @@ pub fn permutation(m: &Bound) -> PyResult<()> { m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; m.add_function(wrap_pyfunction!(_pattern_to_swaps, m)?)?; + m.add_function(wrap_pyfunction!(_synth_permutation_acg, m)?)?; Ok(()) } diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index f263eaddc11e..0da376149eeb 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -20,6 +20,7 @@ _get_ordered_swap, _pattern_to_swaps, ) +from qiskit._accelerate.permutation import _synth_permutation_acg def synth_permutation_basic(pattern: list[int] | np.ndarray[int]) -> QuantumCircuit: @@ -85,6 +86,7 @@ def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircui *Routing Permutations on Graphs Via Matchings.*, `(Full paper) `_ """ + return QuantumCircuit._from_circuit_data(_synth_permutation_acg(pattern)) num_qubits = len(pattern) qc = QuantumCircuit(num_qubits) From 4beba74e6e3a5076358d7943c3d4041115b640b7 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 17 Jun 2024 13:25:58 +0200 Subject: [PATCH 16/21] remove dangling code --- qiskit/synthesis/permutation/permutation_full.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index 0da376149eeb..3b0ea4d7ea02 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -16,11 +16,11 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit._accelerate.permutation import _synth_permutation_acg from .permutation_utils import ( _get_ordered_swap, _pattern_to_swaps, ) -from qiskit._accelerate.permutation import _synth_permutation_acg def synth_permutation_basic(pattern: list[int] | np.ndarray[int]) -> QuantumCircuit: @@ -87,15 +87,3 @@ def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircui `(Full paper) `_ """ return QuantumCircuit._from_circuit_data(_synth_permutation_acg(pattern)) - - num_qubits = len(pattern) - qc = QuantumCircuit(num_qubits) - - # Compute the swap layers from the permutation pattern. ``invert_order`` inverts the - # index order to be consistent with Qiskit notation. - swaps = _pattern_to_swaps(pattern, invert_order=True) - - for swap in swaps: - qc.swap(swap[0], swap[1]) - - return qc From a8e5d3e577a5188493e5757124045f21203b77f1 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 17 Jun 2024 16:20:24 +0200 Subject: [PATCH 17/21] more lint --- qiskit/synthesis/permutation/permutation_full.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index 3b0ea4d7ea02..0ecca1793242 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -17,10 +17,7 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit._accelerate.permutation import _synth_permutation_acg -from .permutation_utils import ( - _get_ordered_swap, - _pattern_to_swaps, -) +from .permutation_utils import _get_ordered_swap def synth_permutation_basic(pattern: list[int] | np.ndarray[int]) -> QuantumCircuit: From 2c0e33d8f98a9781526de8c250c8651d02cd1a7c Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Wed, 19 Jun 2024 11:25:28 +0200 Subject: [PATCH 18/21] add reno --- releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml diff --git a/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml b/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml new file mode 100644 index 000000000000..6bd6761e0355 --- /dev/null +++ b/releasenotes/notes/oxidize-acg-0294a87c0d5974fa.yaml @@ -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. From 7cfa7f5c73ee047c444a7698783e3f948f8e418c Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Fri, 21 Jun 2024 10:05:58 +0200 Subject: [PATCH 19/21] drop redundant Ok(expect()) --- crates/accelerate/src/permutation.rs | 30 ++++++++++++---------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs index e662ec4a9b9a..f9113784a659 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/permutation.rs @@ -142,28 +142,24 @@ fn decompose_cycles(cycles: &Vec>) -> Vec<(usize, usize)> { #[pyfunction] #[pyo3(signature = (pattern))] -fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1) -> PyResult> { +fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1) -> PyResult { let view = pattern.as_array(); let num_qubits = view.len(); let cycles = pattern_to_cycles(&view, &true); let swaps = decompose_cycles(&cycles); - Ok(Some( - 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), - ) - .expect("Something went wrong on Qiskit's Python side, nothing to do here!"), - )) - // let swaps_i64: Vec<(i64, i64)> = swaps.iter().map(|&x| (x.0 as i64, x.1 as i64)).collect(); + 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), + ) } /// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. From 0743d2343cd3c0345b68eba47e1e917f7787fe3e Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 1 Jul 2024 11:24:29 +0200 Subject: [PATCH 20/21] Implements Shelly's suggestions --- .../src/synthesis/permutation/mod.rs | 23 ++++++++ .../src/synthesis/permutation/utils.rs | 58 ++++--------------- .../synthesis/permutation/permutation_full.py | 6 +- .../permutation/permutation_utils.py | 6 +- 4 files changed, 39 insertions(+), 54 deletions(-) diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs index bf0ff97848f2..2dbfcc0b3625 100644 --- a/crates/accelerate/src/synthesis/permutation/mod.rs +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -59,10 +59,33 @@ pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1) -> PyRes ) } +#[pyfunction] +#[pyo3(signature = (pattern))] +fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1) -> PyResult { + 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) -> 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(()) } diff --git a/crates/accelerate/src/synthesis/permutation/utils.rs b/crates/accelerate/src/synthesis/permutation/utils.rs index c5676d1f72f6..929679a2ef81 100644 --- a/crates/accelerate/src/synthesis/permutation/utils.rs +++ b/crates/accelerate/src/synthesis/permutation/utils.rs @@ -11,7 +11,6 @@ // that they have been altered from the originals. use ndarray::{Array1, ArrayView1}; -use numpy::PyArrayLike1; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use std::vec::Vec; @@ -64,19 +63,19 @@ pub fn invert(pattern: &ArrayView1) -> Array1 { /// 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) -> Vec<(i64, i64)> { +pub fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(usize, usize)> { let mut permutation: Vec = 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; @@ -86,6 +85,10 @@ pub fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { 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, invert_order: &bool) -> Vec> { // vector keeping track of which elements in the permutation pattern have been visited let mut explored: Vec = vec![false; pattern.len()]; @@ -119,7 +122,10 @@ pub fn pattern_to_cycles(pattern: &ArrayView1, invert_order: &bool) -> Vec< cycles } -/// Given a disjoint cycle decomposition, decomposes every cycle into a SWAP +/// 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, usize)> { let mut swaps: Vec<(usize, usize)> = Vec::new(); @@ -143,45 +149,3 @@ pub fn decompose_cycles(cycles: &Vec>) -> Vec<(usize, usize)> { swaps } - -/// Sorts the input permutation by iterating through the permutation list -/// and putting each element to its correct position via a SWAP (if it's not -/// at the correct position already). If ``n`` is the length of the input -/// permutation, this requires at most ``n`` SWAPs. -/// -/// 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. -#[pyfunction] -#[pyo3(signature = (permutation_in))] -fn _get_ordered_swap(py: Python, permutation_in: PyArrayLike1) -> PyResult { - let view = permutation_in.as_array(); - Ok(get_ordered_swap(&view).to_object(py)) -} - -/// Decompose a SWAP pattern into a series of SWAP gate indices to implement them. -/// For example, let the pattern be [1, 2, 3, 4, 0, 6, 5], which contains the two cycles -/// [1, 2, 3, 4, 0] and [6, 5]. These can then be implemented with the SWAPs -/// [(0, 3), (1, 2), (0, 4), (1, 3)], respectively [(6, 5)]. -/// If ``invert`` is True, reverse the indices before computing the SWAPs. -#[pyfunction] -#[pyo3(signature = (pattern, invert_order=true))] -fn _pattern_to_swaps( - py: Python, - pattern: PyArrayLike1, - invert_order: bool, -) -> PyResult { - let view = pattern.as_array(); - let cycles = pattern_to_cycles(&view, &invert_order); - let swaps = decompose_cycles(&cycles); - let swaps_i64: Vec<(i64, i64)> = swaps.iter().map(|&x| (x.0 as i64, x.1 as i64)).collect(); - Ok(swaps_i64.to_object(py)) -} - -#[pymodule] -pub fn permutation(m: &Bound) -> PyResult<()> { - m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; - m.add_function(wrap_pyfunction!(_pattern_to_swaps, m)?)?; - Ok(()) -} diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index 1e2e3b5f0009..2fd892a0427e 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -16,8 +16,10 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit._accelerate.permutation import _synth_permutation_acg -from qiskit._accelerate.synthesis.permutation import _synth_permutation_basic +from qiskit._accelerate.synthesis.permutation import ( + _synth_permutation_basic, + _synth_permutation_acg, +) def synth_permutation_basic(pattern: list[int] | np.ndarray[int]) -> QuantumCircuit: diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index f502f14e57c2..a8d18b8a8196 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -13,8 +13,4 @@ """Utility functions for handling permutations.""" # pylint: disable=unused-import -from qiskit._accelerate.synthesis.permutation import ( - _inverse_pattern, - _validate_permutation, - _pattern_to_swaps, -) +from qiskit._accelerate.synthesis.permutation import _inverse_pattern, _validate_permutation From 46a37aa1484028c7cc9d46273fa06e0c4ef2979c Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 2 Jul 2024 15:35:47 +0200 Subject: [PATCH 21/21] simplify code a little --- .../src/synthesis/permutation/mod.rs | 5 +- .../src/synthesis/permutation/utils.rs | 50 ++++++++++--------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs index 2dbfcc0b3625..0e3900a4b8bf 100644 --- a/crates/accelerate/src/synthesis/permutation/mod.rs +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -62,9 +62,10 @@ pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1) -> PyRes #[pyfunction] #[pyo3(signature = (pattern))] fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1) -> PyResult { - let view = pattern.as_array(); + let inverted = utils::invert(&pattern.as_array()); + let view = inverted.view(); let num_qubits = view.len(); - let cycles = utils::pattern_to_cycles(&view, &true); + let cycles = utils::pattern_to_cycles(&view); let swaps = utils::decompose_cycles(&cycles); CircuitData::from_standard_gates( diff --git a/crates/accelerate/src/synthesis/permutation/utils.rs b/crates/accelerate/src/synthesis/permutation/utils.rs index 929679a2ef81..620ce4628741 100644 --- a/crates/accelerate/src/synthesis/permutation/utils.rs +++ b/crates/accelerate/src/synthesis/permutation/utils.rs @@ -15,6 +15,8 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use std::vec::Vec; +use qiskit_circuit::slice::{PySequenceIndex, PySequenceIndexError, SequenceIndex}; + pub fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { let n = pattern.len(); let mut seen: Vec = vec![false; n]; @@ -89,29 +91,22 @@ pub fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(usize, usize)> { /// 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, invert_order: &bool) -> Vec> { +pub fn pattern_to_cycles(pattern: &ArrayView1) -> Vec> { // vector keeping track of which elements in the permutation pattern have been visited let mut explored: Vec = vec![false; pattern.len()]; // vector to store the cycles let mut cycles: Vec> = 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 = if *invert_order { - invert(pattern) // implies cast to usize - } else { - pattern.mapv(|x| x as usize) - }; - - for mut i in permutation.clone() { + for pos in pattern { let mut cycle: Vec = Vec::new(); // follow the cycle until we reached an entry we saw before + let mut i = *pos; while !explored[i] { cycle.push(i); explored[i] = true; - i = permutation[i]; + i = pattern[i]; } // cycles must have more than 1 element if cycle.len() > 1 { @@ -122,6 +117,14 @@ pub fn pattern_to_cycles(pattern: &ArrayView1, invert_order: &bool) -> Vec< cycles } +/// Periodic (or Python-like) access to a vector. +/// Util used below in ``decompose_cycles``. +#[inline] +fn pget(vec: &Vec, index: isize) -> Result { + let SequenceIndex::Int(wrapped) = PySequenceIndex::Int(index).with_len(vec.len())? else {unreachable!()}; + Ok(vec[wrapped]) +} + /// 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 @@ -130,20 +133,19 @@ pub fn decompose_cycles(cycles: &Vec>) -> 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])); - } - } + let length = cycle.len() as isize; - // 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])); + for idx in 0..(length - 1) / 2 { + swaps.push(( + pget(cycle, idx - 1).unwrap(), + pget(cycle, length - 3 - idx).unwrap(), + )); + } + for idx in 0..length / 2 { + swaps.push(( + pget(cycle, idx - 1).unwrap(), + pget(cycle, length - 2 - idx).unwrap(), + )); } }