Skip to content

Commit

Permalink
Merge branch 'main' into oxidize_dcx
Browse files Browse the repository at this point in the history
  • Loading branch information
eliarbel committed Jun 25, 2024
2 parents 2ecf76b + b7269b6 commit f4f563f
Show file tree
Hide file tree
Showing 26 changed files with 592 additions and 201 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ num-traits = "0.2"
num-complex.workspace = true
num-bigint = "0.4"
rustworkx-core = "0.14"
faer = "0.19.0"
faer = "0.19.1"
itertools = "0.13.0"
qiskit-circuit.workspace = true

Expand Down
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(())
}
39 changes: 35 additions & 4 deletions crates/circuit/src/circuit_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
use std::cell::RefCell;

use pyo3::basic::CompareOp;
use pyo3::exceptions::PyValueError;
use pyo3::exceptions::{PyDeprecationWarning, PyValueError};
use pyo3::prelude::*;
use pyo3::types::{IntoPyDict, PyList, PyTuple, PyType};
use pyo3::{intern, IntoPy, PyObject, PyResult};
use smallvec::{smallvec, SmallVec};

use crate::imports::{
get_std_gate_class, populate_std_gate_map, GATE, INSTRUCTION, OPERATION,
SINGLETON_CONTROLLED_GATE, SINGLETON_GATE,
SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, WARNINGS_WARN,
};
use crate::interner::Index;
use crate::operations::{OperationType, Param, PyGate, PyInstruction, PyOperation, StandardGate};
Expand Down Expand Up @@ -572,26 +572,31 @@ impl CircuitInstruction {

#[cfg(not(feature = "cache_pygates"))]
pub fn __getitem__(&self, py: Python<'_>, key: &Bound<PyAny>) -> PyResult<PyObject> {
warn_on_legacy_circuit_instruction_iteration(py)?;
Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py))
}

#[cfg(feature = "cache_pygates")]
pub fn __getitem__(&mut self, py: Python<'_>, key: &Bound<PyAny>) -> PyResult<PyObject> {
warn_on_legacy_circuit_instruction_iteration(py)?;
Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py))
}

#[cfg(not(feature = "cache_pygates"))]
pub fn __iter__(&self, py: Python<'_>) -> PyResult<PyObject> {
warn_on_legacy_circuit_instruction_iteration(py)?;
Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py))
}

#[cfg(feature = "cache_pygates")]
pub fn __iter__(&mut self, py: Python<'_>) -> PyResult<PyObject> {
warn_on_legacy_circuit_instruction_iteration(py)?;
Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py))
}

pub fn __len__(&self) -> usize {
3
pub fn __len__(&self, py: Python) -> PyResult<usize> {
warn_on_legacy_circuit_instruction_iteration(py)?;
Ok(3)
}

pub fn __richcmp__(
Expand Down Expand Up @@ -939,3 +944,29 @@ pub(crate) fn convert_py_to_operation_type(
}
Err(PyValueError::new_err(format!("Invalid input: {}", py_op)))
}

/// Issue a Python `DeprecationWarning` about using the legacy tuple-like interface to
/// `CircuitInstruction`.
///
/// Beware the `stacklevel` here doesn't work quite the same way as it does in Python as Rust-space
/// calls are completely transparent to Python.
#[inline]
fn warn_on_legacy_circuit_instruction_iteration(py: Python) -> PyResult<()> {
WARNINGS_WARN
.get_bound(py)
.call1((
intern!(
py,
concat!(
"Treating CircuitInstruction as an iterable is deprecated legacy behavior",
" since Qiskit 1.2, and will be removed in Qiskit 2.0.",
" Instead, use the `operation`, `qubits` and `clbits` named attributes."
)
),
py.get_type_bound::<PyDeprecationWarning>(),
// Stack level. Compared to Python-space calls to `warn`, this is unusually low
// beacuse all our internal call structure is now Rust-space and invisible to Python.
1,
))
.map(|_| ())
}
32 changes: 32 additions & 0 deletions crates/circuit/src/gate_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,38 @@ pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] {
]
}

#[inline]
pub fn u1_gate(lam: f64) -> [[Complex64; 2]; 2] {
[
[c64(1., 0.), c64(0., 0.)],
[c64(0., 0.), c64(0., lam).exp()],
]
}

#[inline]
pub fn u2_gate(phi: f64, lam: f64) -> [[Complex64; 2]; 2] {
[
[
c64(FRAC_1_SQRT_2, 0.),
(-c64(0., lam).exp()) * FRAC_1_SQRT_2,
],
[
c64(0., phi).exp() * FRAC_1_SQRT_2,
c64(0., phi + lam).exp() * FRAC_1_SQRT_2,
],
]
}

#[inline]
pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] {
let cos = (theta / 2.).cos();
let sin = (theta / 2.).sin();
[
[c64(cos, 0.), -(c64(0., lam).exp()) * sin],
[c64(0., phi).exp() * sin, c64(0., phi + lam).exp() * cos],
]
}

#[inline]
pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] {
let cos = (theta / 2.).cos();
Expand Down
Loading

0 comments on commit f4f563f

Please sign in to comment.