Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Follow up on #12327: circuit construction in Rust #12605

Merged
merged 4 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/accelerate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ 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;
pub mod sparse_pauli_op;
pub mod stochastic_swap;
pub mod synthesis;
pub mod two_qubit_decompose;
pub mod uc_gate;
pub mod utils;
Expand Down
22 changes: 22 additions & 0 deletions crates/accelerate/src/synthesis/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.

mod permutation;

use pyo3::prelude::*;
use pyo3::wrap_pymodule;

#[pymodule]
pub fn synthesis(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pymodule!(permutation::permutation))?;
Ok(())
}
68 changes: 68 additions & 0 deletions crates/accelerate/src/synthesis/permutation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 numpy::PyArrayLike1;
use smallvec::smallvec;

use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

use qiskit_circuit::circuit_data::CircuitData;
use qiskit_circuit::operations::{Param, StandardGate};
use qiskit_circuit::Qubit;

mod utils;

/// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1.
#[pyfunction]
#[pyo3(signature = (pattern))]
pub fn _validate_permutation(py: Python, pattern: PyArrayLike1<i64>) -> PyResult<PyObject> {
let view = pattern.as_array();
utils::validate_permutation(&view)?;
Ok(py.None())
}

/// Finds inverse of a permutation pattern.
#[pyfunction]
#[pyo3(signature = (pattern))]
pub fn _inverse_pattern(py: Python, pattern: PyArrayLike1<i64>) -> PyResult<PyObject> {
let view = pattern.as_array();
let inverse_i64: Vec<i64> = utils::invert(&view).iter().map(|&x| x as i64).collect();
Ok(inverse_i64.to_object(py))
}

#[pyfunction]
#[pyo3(signature = (pattern))]
pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1<i64>) -> PyResult<CircuitData> {
let view = pattern.as_array();
let num_qubits = view.len();
CircuitData::from_standard_gates(
py,
num_qubits as u32,
utils::get_ordered_swap(&view).iter().map(|(i, j)| {
(
StandardGate::SwapGate,
smallvec![],
smallvec![Qubit(*i as u32), Qubit(*j as u32)],
)
}),
Param::Float(0.0),
)
}

#[pymodule]
pub fn permutation(m: &Bound<PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?;
m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?;
m.add_function(wrap_pyfunction!(_synth_permutation_basic, m)?)?;
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
// 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;

fn validate_permutation(pattern: &ArrayView1<i64>) -> PyResult<()> {
pub fn validate_permutation(pattern: &ArrayView1<i64>) -> PyResult<()> {
let n = pattern.len();
let mut seen: Vec<bool> = vec![false; n];

Expand Down Expand Up @@ -47,15 +46,24 @@ fn validate_permutation(pattern: &ArrayView1<i64>) -> PyResult<()> {
Ok(())
}

fn invert(pattern: &ArrayView1<i64>) -> Array1<usize> {
pub fn invert(pattern: &ArrayView1<i64>) -> Array1<usize> {
let mut inverse: Array1<usize> = Array1::zeros(pattern.len());
pattern.iter().enumerate().for_each(|(ii, &jj)| {
inverse[jj as usize] = ii;
});
inverse
}

fn get_ordered_swap(pattern: &ArrayView1<i64>) -> Vec<(i64, i64)> {
/// 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.
pub fn get_ordered_swap(pattern: &ArrayView1<i64>) -> Vec<(i64, i64)> {
let mut permutation: Vec<usize> = pattern.iter().map(|&x| x as usize).collect();
let mut index_map = invert(pattern);

Expand All @@ -76,45 +84,3 @@ fn get_ordered_swap(pattern: &ArrayView1<i64>) -> Vec<(i64, i64)> {
swaps[..].reverse();
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<i64>) -> PyResult<PyObject> {
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<i64>) -> PyResult<PyObject> {
let view = pattern.as_array();
let inverse_i64: Vec<i64> = 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<i64>) -> PyResult<PyObject> {
let view = permutation_in.as_array();
Ok(get_ordered_swap(&view).to_object(py))
}

#[pymodule]
pub fn permutation(m: &Bound<PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?;
m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?;
m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?;
Ok(())
}
10 changes: 5 additions & 5 deletions crates/pyext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ 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, 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,
pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val,
sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap, synthesis::synthesis,
two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils,
vf2_layout::vf2_layout,
};

#[pymodule]
Expand All @@ -36,7 +36,7 @@ fn _accelerate(m: &Bound<PyModule>) -> 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!(synthesis))?;
m.add_wrapped(wrap_pymodule!(results))?;
m.add_wrapped(wrap_pymodule!(sabre))?;
m.add_wrapped(wrap_pymodule!(sampled_exp_val))?;
Expand Down
2 changes: 1 addition & 1 deletion qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
sys.modules["qiskit._accelerate.stochastic_swap"] = _accelerate.stochastic_swap
sys.modules["qiskit._accelerate.two_qubit_decompose"] = _accelerate.two_qubit_decompose
sys.modules["qiskit._accelerate.vf2_layout"] = _accelerate.vf2_layout
sys.modules["qiskit._accelerate.permutation"] = _accelerate.permutation
sys.modules["qiskit._accelerate.synthesis.permutation"] = _accelerate.synthesis.permutation

from qiskit.exceptions import QiskitError, MissingOptionalLibraryError

Expand Down
17 changes: 8 additions & 9 deletions qiskit/circuit/library/generalized_gates/permutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,13 @@ def __init__(

name = "permutation_" + np.array_str(pattern).replace(" ", ",")

circuit = QuantumCircuit(num_qubits, name=name)

super().__init__(num_qubits, name=name)

# pylint: disable=cyclic-import
from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap
from qiskit.synthesis.permutation import synth_permutation_basic

for i, j in _get_ordered_swap(pattern):
circuit.swap(i, j)
circuit = synth_permutation_basic(pattern)
circuit.name = name

all_qubits = self.qubits
self.append(circuit.to_gate(), all_qubits)
Expand Down Expand Up @@ -184,10 +182,11 @@ def inverse(self, annotated: bool = False):

def _qasm2_decomposition(self):
# pylint: disable=cyclic-import
from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap
from qiskit.synthesis.permutation import synth_permutation_basic

name = f"permutation__{'_'.join(str(n) for n in self.pattern)}_"
out = QuantumCircuit(self.num_qubits, name=name)
for i, j in _get_ordered_swap(self.pattern):
out.swap(i, j)

out = synth_permutation_basic(self.pattern)
out.name = name

return out.to_gate()
14 changes: 2 additions & 12 deletions qiskit/synthesis/permutation/permutation_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit._accelerate.synthesis.permutation import _synth_permutation_basic
from .permutation_utils import (
_get_ordered_swap,
_inverse_pattern,
_pattern_to_cycles,
_decompose_cycles,
Expand All @@ -44,17 +44,7 @@ def synth_permutation_basic(pattern: list[int] | np.ndarray[int]) -> QuantumCirc
Returns:
The synthesized quantum circuit.
"""
# This is the very original Qiskit algorithm for synthesizing permutations.

num_qubits = len(pattern)
qc = QuantumCircuit(num_qubits)

swaps = _get_ordered_swap(pattern)

for swap in swaps:
qc.swap(swap[0], swap[1])

return qc
return QuantumCircuit._from_circuit_data(_synth_permutation_basic(pattern))


def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircuit:
Expand Down
3 changes: 1 addition & 2 deletions qiskit/synthesis/permutation/permutation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@
"""Utility functions for handling permutations."""

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

Expand Down
4 changes: 4 additions & 0 deletions releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
upgrade_synthesis:
- |
Port :func:`.synth_permutation_basic`, used to synthesize qubit permutations, to Rust.
14 changes: 0 additions & 14 deletions test/python/synthesis/test_permutation_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
)
from qiskit.synthesis.permutation.permutation_utils import (
_inverse_pattern,
_get_ordered_swap,
_validate_permutation,
)
from test import QiskitTestCase # pylint: disable=wrong-import-order
Expand All @@ -47,19 +46,6 @@ def test_inverse_pattern(self, width):
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."""
np.random.seed(1)
for _ in range(5):
pattern = np.random.permutation(width)
swap_list = _get_ordered_swap(pattern)
output = list(range(width))
for i, j in swap_list:
output[i], output[j] = output[j], output[i]
self.assertTrue(np.array_equal(pattern, output))
self.assertLess(len(swap_list), width)

@data(10, 20)
def test_invalid_permutations(self, width):
"""Check that _validate_permutation raises exceptions when the
Expand Down
Loading