Skip to content

Commit

Permalink
Merge branch 'main' into move-target
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss authored Jul 2, 2024
2 parents 98eaa45 + c674913 commit 7b14c36
Show file tree
Hide file tree
Showing 20 changed files with 451 additions and 155 deletions.
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions crates/accelerate/src/synthesis/permutation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,34 @@ pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1<i64>) -> PyRes
)
}

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

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

#[pymodule]
pub fn permutation(m: &Bound<PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?;
m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?;
m.add_function(wrap_pyfunction!(_synth_permutation_basic, m)?)?;
m.add_function(wrap_pyfunction!(_synth_permutation_acg, m)?)?;
Ok(())
}
73 changes: 70 additions & 3 deletions crates/accelerate/src/synthesis/permutation/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<i64>) -> PyResult<()> {
let n = pattern.len();
let mut seen: Vec<bool> = vec![false; n];
Expand Down Expand Up @@ -63,19 +65,19 @@ pub fn invert(pattern: &ArrayView1<i64>) -> Array1<usize> {
/// then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``);
/// if the input permutation consists of several disjoint cycles, then each cycle
/// is essentially treated independently.
pub fn get_ordered_swap(pattern: &ArrayView1<i64>) -> Vec<(i64, i64)> {
pub fn get_ordered_swap(pattern: &ArrayView1<i64>) -> Vec<(usize, usize)> {
let mut permutation: Vec<usize> = pattern.iter().map(|&x| x as usize).collect();
let mut index_map = invert(pattern);

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

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

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

for pos in pattern {
let mut cycle: Vec<usize> = 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 = pattern[i];
}
// cycles must have more than 1 element
if cycle.len() > 1 {
cycles.push(cycle);
}
}

cycles
}

/// Periodic (or Python-like) access to a vector.
/// Util used below in ``decompose_cycles``.
#[inline]
fn pget(vec: &Vec<usize>, index: isize) -> Result<usize, PySequenceIndexError> {
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
/// full permutation pattern by applying SWAP gates on the returned index-pairs.
pub fn decompose_cycles(cycles: &Vec<Vec<usize>>) -> Vec<(usize, usize)> {
let mut swaps: Vec<(usize, usize)> = Vec::new();

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

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(),
));
}
}

swaps
}
11 changes: 4 additions & 7 deletions crates/circuit/src/circuit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::circuit_instruction::{
convert_py_to_operation_type, CircuitInstruction, ExtraInstructionAttributes, OperationInput,
PackedInstruction,
};
use crate::imports::{BUILTIN_LIST, QUBIT};
use crate::imports::{BUILTIN_LIST, DEEPCOPY, QUBIT};
use crate::interner::{IndexedInterner, Interner, InternerKey};
use crate::operations::{Operation, OperationType, Param, StandardGate};
use crate::parameter_table::{ParamEntry, ParamTable, GLOBAL_PHASE_INDEX};
Expand Down Expand Up @@ -488,20 +488,17 @@ impl CircuitData {
res.param_table.clone_from(&self.param_table);

if deepcopy {
let deepcopy = py
.import_bound(intern!(py, "copy"))?
.getattr(intern!(py, "deepcopy"))?;
for inst in &mut res.data {
match &mut inst.op {
OperationType::Standard(_) => {}
OperationType::Gate(ref mut op) => {
op.gate = deepcopy.call1((&op.gate,))?.unbind();
op.gate = DEEPCOPY.get_bound(py).call1((&op.gate,))?.unbind();
}
OperationType::Instruction(ref mut op) => {
op.instruction = deepcopy.call1((&op.instruction,))?.unbind();
op.instruction = DEEPCOPY.get_bound(py).call1((&op.instruction,))?.unbind();
}
OperationType::Operation(ref mut op) => {
op.operation = deepcopy.call1((&op.operation,))?.unbind();
op.operation = DEEPCOPY.get_bound(py).call1((&op.operation,))?.unbind();
}
};
#[cfg(feature = "cache_pygates")]
Expand Down
55 changes: 54 additions & 1 deletion crates/circuit/src/circuit_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#[cfg(feature = "cache_pygates")]
use std::cell::RefCell;

use numpy::IntoPyArray;
use pyo3::basic::CompareOp;
use pyo3::exceptions::{PyDeprecationWarning, PyValueError};
use pyo3::prelude::*;
Expand All @@ -25,7 +26,9 @@ use crate::imports::{
SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, WARNINGS_WARN,
};
use crate::interner::Index;
use crate::operations::{OperationType, Param, PyGate, PyInstruction, PyOperation, StandardGate};
use crate::operations::{
Operation, OperationType, Param, PyGate, PyInstruction, PyOperation, StandardGate,
};

/// These are extra mutable attributes for a circuit instruction's state. In general we don't
/// typically deal with this in rust space and the majority of the time they're not used in Python
Expand Down Expand Up @@ -407,6 +410,56 @@ impl CircuitInstruction {
})
}

#[getter]
fn _raw_op(&self, py: Python) -> PyObject {
self.operation.clone().into_py(py)
}

/// Returns the Instruction name corresponding to the op for this node
#[getter]
fn get_name(&self, py: Python) -> PyObject {
self.operation.name().to_object(py)
}

#[getter]
fn get_params(&self, py: Python) -> PyObject {
self.params.to_object(py)
}

#[getter]
fn matrix(&self, py: Python) -> Option<PyObject> {
let matrix = self.operation.matrix(&self.params);
matrix.map(|mat| mat.into_pyarray_bound(py).into())
}

#[getter]
fn label(&self) -> Option<&str> {
self.extra_attrs
.as_ref()
.and_then(|attrs| attrs.label.as_deref())
}

#[getter]
fn condition(&self, py: Python) -> Option<PyObject> {
self.extra_attrs
.as_ref()
.and_then(|attrs| attrs.condition.as_ref().map(|x| x.clone_ref(py)))
}

#[getter]
fn duration(&self, py: Python) -> Option<PyObject> {
self.extra_attrs
.as_ref()
.and_then(|attrs| attrs.duration.as_ref().map(|x| x.clone_ref(py)))
}

#[getter]
fn unit(&self) -> Option<&str> {
self.extra_attrs
.as_ref()
.and_then(|attrs| attrs.unit.as_deref())
}

/// Creates a shallow copy with the given fields replaced.
///
/// Returns:
Expand Down
Loading

0 comments on commit 7b14c36

Please sign in to comment.