From 39bab3eb9586d3682052ffda271b7ace72c6ad0c Mon Sep 17 00:00:00 2001 From: jpacold Date: Thu, 2 May 2024 01:41:58 -0600 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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) )