mirror of https://github.com/Qiskit/qiskit.git
Encapsulate Python sequence-like indexers (#12669)
This encapsulates a lot of the common logic around Python sequence-like indexers (`SliceOrInt`) into iterators that handle adapting negative indices and slices in `usize` for containers of a given size. These indexers now all implement `ExactSizeIterator` and `DoubleEndedIterator`, so they can be used with all `Iterator` methods, and can be used (with `Iterator::map` and friends) as inputs to `PyList::new_bound`, which makes code simpler at all points of use. The special-cased uses of this kind of thing from `CircuitData` are replaced with the new forms. This had no measurable impact on performance on my machine, and removes a lot noise from error-handling and highly specialised functions.
This commit is contained in:
parent
a7fc2daf4c
commit
373e8a68c8
|
@ -1180,6 +1180,7 @@ dependencies = [
|
||||||
"rayon",
|
"rayon",
|
||||||
"rustworkx-core",
|
"rustworkx-core",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1192,6 +1193,7 @@ dependencies = [
|
||||||
"numpy",
|
"numpy",
|
||||||
"pyo3",
|
"pyo3",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -20,6 +20,7 @@ num-complex = "0.4"
|
||||||
ndarray = "^0.15.6"
|
ndarray = "^0.15.6"
|
||||||
numpy = "0.21.0"
|
numpy = "0.21.0"
|
||||||
smallvec = "1.13"
|
smallvec = "1.13"
|
||||||
|
thiserror = "1.0"
|
||||||
|
|
||||||
# Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an
|
# Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an
|
||||||
# actual C extension (the feature disables linking in `libpython`, which is forbidden in Python
|
# actual C extension (the feature disables linking in `libpython`, which is forbidden in Python
|
||||||
|
|
|
@ -23,6 +23,7 @@ rustworkx-core = "0.15"
|
||||||
faer = "0.19.1"
|
faer = "0.19.1"
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
qiskit-circuit.workspace = true
|
qiskit-circuit.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
[dependencies.smallvec]
|
[dependencies.smallvec]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -21,9 +21,9 @@ use std::f64::consts::PI;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use pyo3::exceptions::{PyIndexError, PyValueError};
|
use pyo3::exceptions::PyValueError;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PyString;
|
use pyo3::types::{PyList, PyString};
|
||||||
use pyo3::wrap_pyfunction;
|
use pyo3::wrap_pyfunction;
|
||||||
use pyo3::Python;
|
use pyo3::Python;
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ use ndarray::prelude::*;
|
||||||
use numpy::PyReadonlyArray2;
|
use numpy::PyReadonlyArray2;
|
||||||
use pyo3::pybacked::PyBackedStr;
|
use pyo3::pybacked::PyBackedStr;
|
||||||
|
|
||||||
|
use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex};
|
||||||
use qiskit_circuit::util::c64;
|
use qiskit_circuit::util::c64;
|
||||||
use qiskit_circuit::SliceOrInt;
|
|
||||||
|
|
||||||
pub const ANGLE_ZERO_EPSILON: f64 = 1e-12;
|
pub const ANGLE_ZERO_EPSILON: f64 = 1e-12;
|
||||||
|
|
||||||
|
@ -97,46 +97,15 @@ impl OneQubitGateSequence {
|
||||||
Ok(self.gates.len())
|
Ok(self.gates.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __getitem__(&self, py: Python, idx: SliceOrInt) -> PyResult<PyObject> {
|
fn __getitem__(&self, py: Python, idx: PySequenceIndex) -> PyResult<PyObject> {
|
||||||
match idx {
|
match idx.with_len(self.gates.len())? {
|
||||||
SliceOrInt::Slice(slc) => {
|
SequenceIndex::Int(idx) => Ok(self.gates[idx].to_object(py)),
|
||||||
let len = self.gates.len().try_into().unwrap();
|
indices => Ok(PyList::new_bound(
|
||||||
let indices = slc.indices(len)?;
|
py,
|
||||||
let mut out_vec: Vec<(String, SmallVec<[f64; 3]>)> = Vec::new();
|
indices.iter().map(|pos| self.gates[pos].to_object(py)),
|
||||||
// Start and stop will always be positive the slice api converts
|
)
|
||||||
// negatives to the index for example:
|
.into_any()
|
||||||
// list(range(5))[-1:-3:-1]
|
.unbind()),
|
||||||
// will return start=4, stop=2, and step=-1
|
|
||||||
let mut pos: isize = indices.start;
|
|
||||||
let mut cond = if indices.step < 0 {
|
|
||||||
pos > indices.stop
|
|
||||||
} else {
|
|
||||||
pos < indices.stop
|
|
||||||
};
|
|
||||||
while cond {
|
|
||||||
if pos < len as isize {
|
|
||||||
out_vec.push(self.gates[pos as usize].clone());
|
|
||||||
}
|
|
||||||
pos += indices.step;
|
|
||||||
if indices.step < 0 {
|
|
||||||
cond = pos > indices.stop;
|
|
||||||
} else {
|
|
||||||
cond = pos < indices.stop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(out_vec.into_py(py))
|
|
||||||
}
|
|
||||||
SliceOrInt::Int(idx) => {
|
|
||||||
let len = self.gates.len() as isize;
|
|
||||||
if idx >= len || idx < -len {
|
|
||||||
Err(PyIndexError::new_err(format!("Invalid index, {idx}")))
|
|
||||||
} else if idx < 0 {
|
|
||||||
let len = self.gates.len();
|
|
||||||
Ok(self.gates[len - idx.unsigned_abs()].to_object(py))
|
|
||||||
} else {
|
|
||||||
Ok(self.gates[idx as usize].to_object(py))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,6 @@
|
||||||
use approx::{abs_diff_eq, relative_eq};
|
use approx::{abs_diff_eq, relative_eq};
|
||||||
use num_complex::{Complex, Complex64, ComplexFloat};
|
use num_complex::{Complex, Complex64, ComplexFloat};
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use pyo3::exceptions::{PyIndexError, PyValueError};
|
|
||||||
use pyo3::prelude::*;
|
|
||||||
use pyo3::wrap_pyfunction;
|
|
||||||
use pyo3::Python;
|
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use std::f64::consts::{FRAC_1_SQRT_2, PI};
|
use std::f64::consts::{FRAC_1_SQRT_2, PI};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
@ -37,7 +33,11 @@ use ndarray::prelude::*;
|
||||||
use ndarray::Zip;
|
use ndarray::Zip;
|
||||||
use numpy::PyReadonlyArray2;
|
use numpy::PyReadonlyArray2;
|
||||||
use numpy::{IntoPyArray, ToPyArray};
|
use numpy::{IntoPyArray, ToPyArray};
|
||||||
|
|
||||||
|
use pyo3::exceptions::PyValueError;
|
||||||
|
use pyo3::prelude::*;
|
||||||
use pyo3::pybacked::PyBackedStr;
|
use pyo3::pybacked::PyBackedStr;
|
||||||
|
use pyo3::types::PyList;
|
||||||
|
|
||||||
use crate::convert_2q_block_matrix::change_basis;
|
use crate::convert_2q_block_matrix::change_basis;
|
||||||
use crate::euler_one_qubit_decomposer::{
|
use crate::euler_one_qubit_decomposer::{
|
||||||
|
@ -52,8 +52,8 @@ use rand_distr::StandardNormal;
|
||||||
use rand_pcg::Pcg64Mcg;
|
use rand_pcg::Pcg64Mcg;
|
||||||
|
|
||||||
use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE};
|
use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE};
|
||||||
|
use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex};
|
||||||
use qiskit_circuit::util::{c64, GateArray1Q, GateArray2Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM};
|
use qiskit_circuit::util::{c64, GateArray1Q, GateArray2Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM};
|
||||||
use qiskit_circuit::SliceOrInt;
|
|
||||||
|
|
||||||
const PI2: f64 = PI / 2.;
|
const PI2: f64 = PI / 2.;
|
||||||
const PI4: f64 = PI / 4.;
|
const PI4: f64 = PI / 4.;
|
||||||
|
@ -1131,46 +1131,15 @@ impl TwoQubitGateSequence {
|
||||||
Ok(self.gates.len())
|
Ok(self.gates.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __getitem__(&self, py: Python, idx: SliceOrInt) -> PyResult<PyObject> {
|
fn __getitem__(&self, py: Python, idx: PySequenceIndex) -> PyResult<PyObject> {
|
||||||
match idx {
|
match idx.with_len(self.gates.len())? {
|
||||||
SliceOrInt::Slice(slc) => {
|
SequenceIndex::Int(idx) => Ok(self.gates[idx].to_object(py)),
|
||||||
let len = self.gates.len().try_into().unwrap();
|
indices => Ok(PyList::new_bound(
|
||||||
let indices = slc.indices(len)?;
|
py,
|
||||||
let mut out_vec: TwoQubitSequenceVec = Vec::new();
|
indices.iter().map(|pos| self.gates[pos].to_object(py)),
|
||||||
// Start and stop will always be positive the slice api converts
|
)
|
||||||
// negatives to the index for example:
|
.into_any()
|
||||||
// list(range(5))[-1:-3:-1]
|
.unbind()),
|
||||||
// will return start=4, stop=2, and step=-
|
|
||||||
let mut pos: isize = indices.start;
|
|
||||||
let mut cond = if indices.step < 0 {
|
|
||||||
pos > indices.stop
|
|
||||||
} else {
|
|
||||||
pos < indices.stop
|
|
||||||
};
|
|
||||||
while cond {
|
|
||||||
if pos < len as isize {
|
|
||||||
out_vec.push(self.gates[pos as usize].clone());
|
|
||||||
}
|
|
||||||
pos += indices.step;
|
|
||||||
if indices.step < 0 {
|
|
||||||
cond = pos > indices.stop;
|
|
||||||
} else {
|
|
||||||
cond = pos < indices.stop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(out_vec.into_py(py))
|
|
||||||
}
|
|
||||||
SliceOrInt::Int(idx) => {
|
|
||||||
let len = self.gates.len() as isize;
|
|
||||||
if idx >= len || idx < -len {
|
|
||||||
Err(PyIndexError::new_err(format!("Invalid index, {idx}")))
|
|
||||||
} else if idx < 0 {
|
|
||||||
let len = self.gates.len();
|
|
||||||
Ok(self.gates[len - idx.unsigned_abs()].to_object(py))
|
|
||||||
} else {
|
|
||||||
Ok(self.gates[idx as usize].to_object(py))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ hashbrown.workspace = true
|
||||||
num-complex.workspace = true
|
num-complex.workspace = true
|
||||||
ndarray.workspace = true
|
ndarray.workspace = true
|
||||||
numpy.workspace = true
|
numpy.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
[dependencies.pyo3]
|
[dependencies.pyo3]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -22,11 +22,12 @@ use crate::imports::{BUILTIN_LIST, QUBIT};
|
||||||
use crate::interner::{IndexedInterner, Interner, InternerKey};
|
use crate::interner::{IndexedInterner, Interner, InternerKey};
|
||||||
use crate::operations::{Operation, OperationType, Param, StandardGate};
|
use crate::operations::{Operation, OperationType, Param, StandardGate};
|
||||||
use crate::parameter_table::{ParamEntry, ParamTable, GLOBAL_PHASE_INDEX};
|
use crate::parameter_table::{ParamEntry, ParamTable, GLOBAL_PHASE_INDEX};
|
||||||
use crate::{Clbit, Qubit, SliceOrInt};
|
use crate::slice::{PySequenceIndex, SequenceIndex};
|
||||||
|
use crate::{Clbit, Qubit};
|
||||||
|
|
||||||
use pyo3::exceptions::{PyIndexError, PyValueError};
|
use pyo3::exceptions::{PyIndexError, PyValueError};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyList, PySet, PySlice, PyTuple, PyType};
|
use pyo3::types::{PyList, PySet, PyTuple, PyType};
|
||||||
use pyo3::{intern, PyTraverseError, PyVisit};
|
use pyo3::{intern, PyTraverseError, PyVisit};
|
||||||
|
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
|
@ -321,7 +322,7 @@ impl CircuitData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append_inner(&mut self, py: Python, value: PyRef<CircuitInstruction>) -> PyResult<bool> {
|
pub fn append_inner(&mut self, py: Python, value: PyRef<CircuitInstruction>) -> PyResult<bool> {
|
||||||
let packed = self.pack(py, value)?;
|
let packed = self.pack(value)?;
|
||||||
let new_index = self.data.len();
|
let new_index = self.data.len();
|
||||||
self.data.push(packed);
|
self.data.push(packed);
|
||||||
self.update_param_table(py, new_index, None)
|
self.update_param_table(py, new_index, None)
|
||||||
|
@ -744,184 +745,130 @@ impl CircuitData {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we also rely on this to make us iterable!
|
// Note: we also rely on this to make us iterable!
|
||||||
pub fn __getitem__(&self, py: Python, index: &Bound<PyAny>) -> PyResult<PyObject> {
|
pub fn __getitem__(&self, py: Python, index: PySequenceIndex) -> PyResult<PyObject> {
|
||||||
// Internal helper function to get a specific
|
// Get a single item, assuming the index is validated as in bounds.
|
||||||
// instruction by index.
|
let get_single = |index: usize| {
|
||||||
fn get_at(
|
let inst = &self.data[index];
|
||||||
self_: &CircuitData,
|
let qubits = self.qargs_interner.intern(inst.qubits_id);
|
||||||
py: Python<'_>,
|
let clbits = self.cargs_interner.intern(inst.clbits_id);
|
||||||
index: isize,
|
CircuitInstruction::new(
|
||||||
) -> PyResult<Py<CircuitInstruction>> {
|
py,
|
||||||
let index = self_.convert_py_index(index)?;
|
inst.op.clone(),
|
||||||
if let Some(inst) = self_.data.get(index) {
|
self.qubits.map_indices(qubits.value),
|
||||||
let qubits = self_.qargs_interner.intern(inst.qubits_id);
|
self.clbits.map_indices(clbits.value),
|
||||||
let clbits = self_.cargs_interner.intern(inst.clbits_id);
|
inst.params.clone(),
|
||||||
Py::new(
|
inst.extra_attrs.clone(),
|
||||||
py,
|
)
|
||||||
CircuitInstruction::new(
|
.into_py(py)
|
||||||
py,
|
};
|
||||||
inst.op.clone(),
|
match index.with_len(self.data.len())? {
|
||||||
self_.qubits.map_indices(qubits.value),
|
SequenceIndex::Int(index) => Ok(get_single(index)),
|
||||||
self_.clbits.map_indices(clbits.value),
|
indices => Ok(PyList::new_bound(py, indices.iter().map(get_single)).into_py(py)),
|
||||||
inst.params.clone(),
|
|
||||||
inst.extra_attrs.clone(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Err(PyIndexError::new_err(format!(
|
|
||||||
"No element at index {:?} in circuit data",
|
|
||||||
index
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if index.is_exact_instance_of::<PySlice>() {
|
|
||||||
let slice = self.convert_py_slice(index.downcast_exact::<PySlice>()?)?;
|
|
||||||
let result = slice
|
|
||||||
.into_iter()
|
|
||||||
.map(|i| get_at(self, py, i))
|
|
||||||
.collect::<PyResult<Vec<_>>>()?;
|
|
||||||
Ok(result.into_py(py))
|
|
||||||
} else {
|
|
||||||
Ok(get_at(self, py, index.extract()?)?.into_py(py))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn __delitem__(&mut self, py: Python, index: SliceOrInt) -> PyResult<()> {
|
pub fn __delitem__(&mut self, py: Python, index: PySequenceIndex) -> PyResult<()> {
|
||||||
match index {
|
self.delitem(py, index.with_len(self.data.len())?)
|
||||||
SliceOrInt::Slice(slice) => {
|
}
|
||||||
let slice = {
|
|
||||||
let mut s = self.convert_py_slice(&slice)?;
|
pub fn setitem_no_param_table_update(
|
||||||
if s.len() > 1 && s.first().unwrap() < s.last().unwrap() {
|
&mut self,
|
||||||
// Reverse the order so we're sure to delete items
|
index: usize,
|
||||||
// at the back first (avoids messing up indices).
|
value: PyRef<CircuitInstruction>,
|
||||||
s.reverse()
|
) -> PyResult<()> {
|
||||||
}
|
let mut packed = self.pack(value)?;
|
||||||
s
|
std::mem::swap(&mut packed, &mut self.data[index]);
|
||||||
};
|
Ok(())
|
||||||
for i in slice.into_iter() {
|
}
|
||||||
self.__delitem__(py, SliceOrInt::Int(i))?;
|
|
||||||
|
pub fn __setitem__(&mut self, index: PySequenceIndex, value: &Bound<PyAny>) -> PyResult<()> {
|
||||||
|
fn set_single(slf: &mut CircuitData, index: usize, value: &Bound<PyAny>) -> PyResult<()> {
|
||||||
|
let py = value.py();
|
||||||
|
let mut packed = slf.pack(value.downcast::<CircuitInstruction>()?.borrow())?;
|
||||||
|
slf.remove_from_parameter_table(py, index)?;
|
||||||
|
std::mem::swap(&mut packed, &mut slf.data[index]);
|
||||||
|
slf.update_param_table(py, index, None)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let py = value.py();
|
||||||
|
match index.with_len(self.data.len())? {
|
||||||
|
SequenceIndex::Int(index) => set_single(self, index, value),
|
||||||
|
indices @ SequenceIndex::PosRange {
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
step: 1,
|
||||||
|
} => {
|
||||||
|
// `list` allows setting a slice with step +1 to an arbitrary length.
|
||||||
|
let values = value.iter()?.collect::<PyResult<Vec<_>>>()?;
|
||||||
|
for (index, value) in indices.iter().zip(values.iter()) {
|
||||||
|
set_single(self, index, value)?;
|
||||||
|
}
|
||||||
|
if indices.len() > values.len() {
|
||||||
|
self.delitem(
|
||||||
|
py,
|
||||||
|
SequenceIndex::PosRange {
|
||||||
|
start: start + values.len(),
|
||||||
|
stop,
|
||||||
|
step: 1,
|
||||||
|
},
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
for value in values[indices.len()..].iter().rev() {
|
||||||
|
self.insert(stop as isize, value.downcast()?.borrow())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.reindex_parameter_table(py)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
SliceOrInt::Int(index) => {
|
indices => {
|
||||||
let index = self.convert_py_index(index)?;
|
let values = value.iter()?.collect::<PyResult<Vec<_>>>()?;
|
||||||
if self.data.get(index).is_some() {
|
if indices.len() == values.len() {
|
||||||
if index == self.data.len() {
|
for (index, value) in indices.iter().zip(values.iter()) {
|
||||||
// For individual removal from param table before
|
set_single(self, index, value)?;
|
||||||
// deletion
|
|
||||||
self.remove_from_parameter_table(py, index)?;
|
|
||||||
self.data.remove(index);
|
|
||||||
} else {
|
|
||||||
// For delete in the middle delete before reindexing
|
|
||||||
self.data.remove(index);
|
|
||||||
self.reindex_parameter_table(py)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(PyIndexError::new_err(format!(
|
Err(PyValueError::new_err(format!(
|
||||||
"No element at index {:?} in circuit data",
|
"attempt to assign sequence of size {:?} to extended slice of size {:?}",
|
||||||
index
|
values.len(),
|
||||||
|
indices.len(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setitem_no_param_table_update(
|
pub fn insert(&mut self, mut index: isize, value: PyRef<CircuitInstruction>) -> PyResult<()> {
|
||||||
&mut self,
|
// `list.insert` has special-case extra clamping logic for its index argument.
|
||||||
py: Python<'_>,
|
let index = {
|
||||||
index: isize,
|
if index < 0 {
|
||||||
value: &Bound<PyAny>,
|
// This can't exceed `isize::MAX` because `self.data[0]` is larger than a byte.
|
||||||
) -> PyResult<()> {
|
index += self.data.len() as isize;
|
||||||
let index = self.convert_py_index(index)?;
|
|
||||||
let value: PyRef<CircuitInstruction> = value.downcast()?.borrow();
|
|
||||||
let mut packed = self.pack(py, value)?;
|
|
||||||
std::mem::swap(&mut packed, &mut self.data[index]);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn __setitem__(
|
|
||||||
&mut self,
|
|
||||||
py: Python<'_>,
|
|
||||||
index: SliceOrInt,
|
|
||||||
value: &Bound<PyAny>,
|
|
||||||
) -> PyResult<()> {
|
|
||||||
match index {
|
|
||||||
SliceOrInt::Slice(slice) => {
|
|
||||||
let indices = slice.indices(self.data.len().try_into().unwrap())?;
|
|
||||||
let slice = self.convert_py_slice(&slice)?;
|
|
||||||
let values = value.iter()?.collect::<PyResult<Vec<Bound<PyAny>>>>()?;
|
|
||||||
if indices.step != 1 && slice.len() != values.len() {
|
|
||||||
// A replacement of a different length when step isn't exactly '1'
|
|
||||||
// would result in holes.
|
|
||||||
return Err(PyValueError::new_err(format!(
|
|
||||||
"attempt to assign sequence of size {:?} to extended slice of size {:?}",
|
|
||||||
values.len(),
|
|
||||||
slice.len(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i, v) in slice.iter().zip(values.iter()) {
|
|
||||||
self.__setitem__(py, SliceOrInt::Int(*i), v)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if slice.len() > values.len() {
|
|
||||||
// Delete any extras.
|
|
||||||
let slice = PySlice::new_bound(
|
|
||||||
py,
|
|
||||||
indices.start + values.len() as isize,
|
|
||||||
indices.stop,
|
|
||||||
1isize,
|
|
||||||
);
|
|
||||||
self.__delitem__(py, SliceOrInt::Slice(slice))?;
|
|
||||||
} else {
|
|
||||||
// Insert any extra values.
|
|
||||||
for v in values.iter().skip(slice.len()).rev() {
|
|
||||||
let v: PyRef<CircuitInstruction> = v.extract()?;
|
|
||||||
self.insert(py, indices.stop, v)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
SliceOrInt::Int(index) => {
|
if index < 0 {
|
||||||
let index = self.convert_py_index(index)?;
|
0
|
||||||
let value: PyRef<CircuitInstruction> = value.extract()?;
|
} else if index as usize > self.data.len() {
|
||||||
let mut packed = self.pack(py, value)?;
|
self.data.len()
|
||||||
self.remove_from_parameter_table(py, index)?;
|
} else {
|
||||||
std::mem::swap(&mut packed, &mut self.data[index]);
|
index as usize
|
||||||
self.update_param_table(py, index, None)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
let py = value.py();
|
||||||
|
let packed = self.pack(value)?;
|
||||||
pub fn insert(
|
|
||||||
&mut self,
|
|
||||||
py: Python<'_>,
|
|
||||||
index: isize,
|
|
||||||
value: PyRef<CircuitInstruction>,
|
|
||||||
) -> PyResult<()> {
|
|
||||||
let index = self.convert_py_index_clamped(index);
|
|
||||||
let old_len = self.data.len();
|
|
||||||
let packed = self.pack(py, value)?;
|
|
||||||
self.data.insert(index, packed);
|
self.data.insert(index, packed);
|
||||||
if index == old_len {
|
if index == self.data.len() - 1 {
|
||||||
self.update_param_table(py, old_len, None)?;
|
self.update_param_table(py, index, None)?;
|
||||||
} else {
|
} else {
|
||||||
self.reindex_parameter_table(py)?;
|
self.reindex_parameter_table(py)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop(&mut self, py: Python<'_>, index: Option<PyObject>) -> PyResult<PyObject> {
|
pub fn pop(&mut self, py: Python<'_>, index: Option<PySequenceIndex>) -> PyResult<PyObject> {
|
||||||
let index =
|
let index = index.unwrap_or(PySequenceIndex::Int(-1));
|
||||||
index.unwrap_or_else(|| std::cmp::max(0, self.data.len() as isize - 1).into_py(py));
|
let native_index = index.with_len(self.data.len())?;
|
||||||
let item = self.__getitem__(py, index.bind(py))?;
|
let item = self.__getitem__(py, index)?;
|
||||||
|
self.delitem(py, native_index)?;
|
||||||
self.__delitem__(py, index.bind(py).extract()?)?;
|
|
||||||
Ok(item)
|
Ok(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -931,7 +878,7 @@ impl CircuitData {
|
||||||
value: &Bound<CircuitInstruction>,
|
value: &Bound<CircuitInstruction>,
|
||||||
params: Option<Vec<(usize, Vec<PyObject>)>>,
|
params: Option<Vec<(usize, Vec<PyObject>)>>,
|
||||||
) -> PyResult<bool> {
|
) -> PyResult<bool> {
|
||||||
let packed = self.pack(py, value.try_borrow()?)?;
|
let packed = self.pack(value.try_borrow()?)?;
|
||||||
let new_index = self.data.len();
|
let new_index = self.data.len();
|
||||||
self.data.push(packed);
|
self.data.push(packed);
|
||||||
self.update_param_table(py, new_index, params)
|
self.update_param_table(py, new_index, params)
|
||||||
|
@ -1175,56 +1122,22 @@ impl CircuitData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CircuitData {
|
impl CircuitData {
|
||||||
/// Converts a Python slice to a `Vec` of indices into
|
/// Native internal driver of `__delitem__` that uses a Rust-space version of the
|
||||||
/// the instruction listing, [CircuitData.data].
|
/// `SequenceIndex`. This assumes that the `SequenceIndex` contains only in-bounds indices, and
|
||||||
fn convert_py_slice(&self, slice: &Bound<PySlice>) -> PyResult<Vec<isize>> {
|
/// panics if not.
|
||||||
let indices = slice.indices(self.data.len().try_into().unwrap())?;
|
fn delitem(&mut self, py: Python, indices: SequenceIndex) -> PyResult<()> {
|
||||||
if indices.step > 0 {
|
// We need to delete in reverse order so we don't invalidate higher indices with a deletion.
|
||||||
Ok((indices.start..indices.stop)
|
for index in indices.descending() {
|
||||||
.step_by(indices.step as usize)
|
self.data.remove(index);
|
||||||
.collect())
|
|
||||||
} else {
|
|
||||||
let mut out = Vec::with_capacity(indices.slicelength as usize);
|
|
||||||
let mut x = indices.start;
|
|
||||||
while x > indices.stop {
|
|
||||||
out.push(x);
|
|
||||||
x += indices.step;
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
}
|
||||||
}
|
if !indices.is_empty() {
|
||||||
|
self.reindex_parameter_table(py)?;
|
||||||
/// Converts a Python index to an index into the instruction listing,
|
|
||||||
/// or one past its end.
|
|
||||||
/// If the resulting index would be < 0, clamps to 0.
|
|
||||||
/// If the resulting index would be > len(data), clamps to len(data).
|
|
||||||
fn convert_py_index_clamped(&self, index: isize) -> usize {
|
|
||||||
let index = if index < 0 {
|
|
||||||
index + self.data.len() as isize
|
|
||||||
} else {
|
|
||||||
index
|
|
||||||
};
|
|
||||||
std::cmp::min(std::cmp::max(0, index), self.data.len() as isize) as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a Python index to an index into the instruction listing.
|
|
||||||
fn convert_py_index(&self, index: isize) -> PyResult<usize> {
|
|
||||||
let index = if index < 0 {
|
|
||||||
index + self.data.len() as isize
|
|
||||||
} else {
|
|
||||||
index
|
|
||||||
};
|
|
||||||
|
|
||||||
if index < 0 || index >= self.data.len() as isize {
|
|
||||||
return Err(PyIndexError::new_err(format!(
|
|
||||||
"Index {:?} is out of bounds.",
|
|
||||||
index,
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
Ok(index as usize)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(&mut self, py: Python, inst: PyRef<CircuitInstruction>) -> PyResult<PackedInstruction> {
|
fn pack(&mut self, inst: PyRef<CircuitInstruction>) -> PyResult<PackedInstruction> {
|
||||||
|
let py = inst.py();
|
||||||
let qubits = Interner::intern(
|
let qubits = Interner::intern(
|
||||||
&mut self.qargs_interner,
|
&mut self.qargs_interner,
|
||||||
InternerKey::Value(self.qubits.map_bits(inst.qubits.bind(py))?.collect()),
|
InternerKey::Value(self.qubits.map_bits(inst.qubits.bind(py))?.collect()),
|
||||||
|
|
|
@ -17,23 +17,13 @@ pub mod gate_matrix;
|
||||||
pub mod imports;
|
pub mod imports;
|
||||||
pub mod operations;
|
pub mod operations;
|
||||||
pub mod parameter_table;
|
pub mod parameter_table;
|
||||||
|
pub mod slice;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
mod bit_data;
|
mod bit_data;
|
||||||
mod interner;
|
mod interner;
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PySlice;
|
|
||||||
|
|
||||||
/// A private enumeration type used to extract arguments to pymethod
|
|
||||||
/// that may be either an index or a slice
|
|
||||||
#[derive(FromPyObject)]
|
|
||||||
pub enum SliceOrInt<'a> {
|
|
||||||
// The order here defines the order the variants are tried in the FromPyObject` derivation.
|
|
||||||
// `Int` is _much_ more common, so that should be first.
|
|
||||||
Int(isize),
|
|
||||||
Slice(Bound<'a, PySlice>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type BitType = u32;
|
pub type BitType = u32;
|
||||||
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
|
|
@ -0,0 +1,375 @@
|
||||||
|
// 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 thiserror::Error;
|
||||||
|
|
||||||
|
use pyo3::exceptions::PyIndexError;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PySlice;
|
||||||
|
|
||||||
|
use self::sealed::{Descending, SequenceIndexIter};
|
||||||
|
|
||||||
|
/// A Python-space indexer for the standard `PySequence` type; a single integer or a slice.
|
||||||
|
///
|
||||||
|
/// These come in as `isize`s from Python space, since Python typically allows negative indices.
|
||||||
|
/// Use `with_len` to specialize the index to a valid Rust-space indexer into a collection of the
|
||||||
|
/// given length.
|
||||||
|
pub enum PySequenceIndex<'py> {
|
||||||
|
Int(isize),
|
||||||
|
Slice(Bound<'py, PySlice>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'py> FromPyObject<'py> for PySequenceIndex<'py> {
|
||||||
|
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
|
||||||
|
// `slice` can't be subclassed in Python, so it's safe (and faster) to check for it exactly.
|
||||||
|
// The `downcast_exact` check is just a pointer comparison, so while `slice` is the less
|
||||||
|
// common input, doing that first has little-to-no impact on the speed of the `isize` path,
|
||||||
|
// while the reverse makes `slice` inputs significantly slower.
|
||||||
|
if let Ok(slice) = ob.downcast_exact::<PySlice>() {
|
||||||
|
return Ok(Self::Slice(slice.clone()));
|
||||||
|
}
|
||||||
|
Ok(Self::Int(ob.extract()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'py> PySequenceIndex<'py> {
|
||||||
|
/// Specialize this index to a collection of the given `len`, returning a Rust-native type.
|
||||||
|
pub fn with_len(&self, len: usize) -> Result<SequenceIndex, PySequenceIndexError> {
|
||||||
|
match self {
|
||||||
|
PySequenceIndex::Int(index) => {
|
||||||
|
let index = if *index >= 0 {
|
||||||
|
let index = *index as usize;
|
||||||
|
if index >= len {
|
||||||
|
return Err(PySequenceIndexError::OutOfRange);
|
||||||
|
}
|
||||||
|
index
|
||||||
|
} else {
|
||||||
|
len.checked_sub(index.unsigned_abs())
|
||||||
|
.ok_or(PySequenceIndexError::OutOfRange)?
|
||||||
|
};
|
||||||
|
Ok(SequenceIndex::Int(index))
|
||||||
|
}
|
||||||
|
PySequenceIndex::Slice(slice) => {
|
||||||
|
let indices = slice
|
||||||
|
.indices(len as ::std::os::raw::c_long)
|
||||||
|
.map_err(PySequenceIndexError::from)?;
|
||||||
|
if indices.step > 0 {
|
||||||
|
Ok(SequenceIndex::PosRange {
|
||||||
|
start: indices.start as usize,
|
||||||
|
stop: indices.stop as usize,
|
||||||
|
step: indices.step as usize,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(SequenceIndex::NegRange {
|
||||||
|
// `indices.start` can be negative if the collection length is 0.
|
||||||
|
start: (indices.start >= 0).then_some(indices.start as usize),
|
||||||
|
// `indices.stop` can be negative if the 0 index should be output.
|
||||||
|
stop: (indices.stop >= 0).then_some(indices.stop as usize),
|
||||||
|
step: indices.step.unsigned_abs(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error type for problems encountered when calling methods on `PySequenceIndex`.
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum PySequenceIndexError {
|
||||||
|
#[error("index out of range")]
|
||||||
|
OutOfRange,
|
||||||
|
#[error(transparent)]
|
||||||
|
InnerPy(#[from] PyErr),
|
||||||
|
}
|
||||||
|
impl From<PySequenceIndexError> for PyErr {
|
||||||
|
fn from(value: PySequenceIndexError) -> PyErr {
|
||||||
|
match value {
|
||||||
|
PySequenceIndexError::OutOfRange => PyIndexError::new_err("index out of range"),
|
||||||
|
PySequenceIndexError::InnerPy(inner) => inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rust-native version of a Python sequence-like indexer.
|
||||||
|
///
|
||||||
|
/// Typically this is constructed by a call to `PySequenceIndex::with_len`, which guarantees that
|
||||||
|
/// all the indices will be in bounds for a collection of the given length.
|
||||||
|
///
|
||||||
|
/// This splits the positive- and negative-step versions of the slice in two so it can be translated
|
||||||
|
/// more easily into static dispatch. This type can be converted into several types of iterator.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum SequenceIndex {
|
||||||
|
Int(usize),
|
||||||
|
PosRange {
|
||||||
|
start: usize,
|
||||||
|
stop: usize,
|
||||||
|
step: usize,
|
||||||
|
},
|
||||||
|
NegRange {
|
||||||
|
start: Option<usize>,
|
||||||
|
stop: Option<usize>,
|
||||||
|
step: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SequenceIndex {
|
||||||
|
/// The number of indices this refers to.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Int(_) => 1,
|
||||||
|
Self::PosRange { start, stop, step } => {
|
||||||
|
let gap = stop.saturating_sub(*start);
|
||||||
|
gap / *step + (gap % *step != 0) as usize
|
||||||
|
}
|
||||||
|
Self::NegRange { start, stop, step } => 'arm: {
|
||||||
|
let Some(start) = start else { break 'arm 0 };
|
||||||
|
let gap = stop
|
||||||
|
.map(|stop| start.saturating_sub(stop))
|
||||||
|
.unwrap_or(*start + 1);
|
||||||
|
gap / step + (gap % step != 0) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
// This is just to keep clippy happy; the length is already fairly inexpensive to calculate.
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterator over the indices. This will be a single-item iterator for the case of
|
||||||
|
/// `Self::Int`, but you probably wanted to destructure off that case beforehand anyway.
|
||||||
|
pub fn iter(&self) -> SequenceIndexIter {
|
||||||
|
match self {
|
||||||
|
Self::Int(value) => SequenceIndexIter::Int(Some(*value)),
|
||||||
|
Self::PosRange { start, step, .. } => SequenceIndexIter::PosRange {
|
||||||
|
lowest: *start,
|
||||||
|
step: *step,
|
||||||
|
indices: 0..self.len(),
|
||||||
|
},
|
||||||
|
Self::NegRange { start, step, .. } => SequenceIndexIter::NegRange {
|
||||||
|
// We can unwrap `highest` to an arbitrary value if `None`, because in that case the
|
||||||
|
// `len` is 0 and the iterator will not yield any objects.
|
||||||
|
highest: start.unwrap_or_default(),
|
||||||
|
step: *step,
|
||||||
|
indices: 0..self.len(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an iterator over the contained indices that is guaranteed to iterate from the highest
|
||||||
|
// index to the lowest.
|
||||||
|
pub fn descending(&self) -> Descending {
|
||||||
|
Descending(self.iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for SequenceIndex {
|
||||||
|
type Item = usize;
|
||||||
|
type IntoIter = SequenceIndexIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private module to make it impossible to construct or inspect the internals of the iterator types
|
||||||
|
// from outside this file, while still allowing them to be used.
|
||||||
|
mod sealed {
|
||||||
|
/// Custom iterator for indices for Python sequence-likes.
|
||||||
|
///
|
||||||
|
/// In the range types, the `indices ` are `Range` objects that run from 0 to the length of the
|
||||||
|
/// iterator. In theory, we could generate the iterators ourselves, but that ends up with a lot of
|
||||||
|
/// boilerplate.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum SequenceIndexIter {
|
||||||
|
Int(Option<usize>),
|
||||||
|
PosRange {
|
||||||
|
lowest: usize,
|
||||||
|
step: usize,
|
||||||
|
indices: ::std::ops::Range<usize>,
|
||||||
|
},
|
||||||
|
NegRange {
|
||||||
|
highest: usize,
|
||||||
|
// The step of the iterator, but note that this is a negative range, so the forwards method
|
||||||
|
// steps downwards from `upper` towards `lower`.
|
||||||
|
step: usize,
|
||||||
|
indices: ::std::ops::Range<usize>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
impl Iterator for SequenceIndexIter {
|
||||||
|
type Item = usize;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self {
|
||||||
|
Self::Int(value) => value.take(),
|
||||||
|
Self::PosRange {
|
||||||
|
lowest,
|
||||||
|
step,
|
||||||
|
indices,
|
||||||
|
} => indices.next().map(|idx| *lowest + idx * *step),
|
||||||
|
Self::NegRange {
|
||||||
|
highest,
|
||||||
|
step,
|
||||||
|
indices,
|
||||||
|
} => indices.next().map(|idx| *highest - idx * *step),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
match self {
|
||||||
|
Self::Int(None) => (0, Some(0)),
|
||||||
|
Self::Int(Some(_)) => (1, Some(1)),
|
||||||
|
Self::PosRange { indices, .. } | Self::NegRange { indices, .. } => {
|
||||||
|
indices.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DoubleEndedIterator for SequenceIndexIter {
|
||||||
|
#[inline]
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
match self {
|
||||||
|
Self::Int(value) => value.take(),
|
||||||
|
Self::PosRange {
|
||||||
|
lowest,
|
||||||
|
step,
|
||||||
|
indices,
|
||||||
|
} => indices.next_back().map(|idx| *lowest + idx * *step),
|
||||||
|
Self::NegRange {
|
||||||
|
highest,
|
||||||
|
step,
|
||||||
|
indices,
|
||||||
|
} => indices.next_back().map(|idx| *highest - idx * *step),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ExactSizeIterator for SequenceIndexIter {}
|
||||||
|
|
||||||
|
pub struct Descending(pub SequenceIndexIter);
|
||||||
|
impl Iterator for Descending {
|
||||||
|
type Item = usize;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self.0 {
|
||||||
|
SequenceIndexIter::Int(_) | SequenceIndexIter::NegRange { .. } => self.0.next(),
|
||||||
|
SequenceIndexIter::PosRange { .. } => self.0.next_back(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.0.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DoubleEndedIterator for Descending {
|
||||||
|
#[inline]
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
match self.0 {
|
||||||
|
SequenceIndexIter::Int(_) | SequenceIndexIter::NegRange { .. } => {
|
||||||
|
self.0.next_back()
|
||||||
|
}
|
||||||
|
SequenceIndexIter::PosRange { .. } => self.0.next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ExactSizeIterator for Descending {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Get a set of test parametrisations for iterator methods. The second argument is the
|
||||||
|
/// expected values from a normal forward iteration.
|
||||||
|
fn index_iterator_cases() -> impl Iterator<Item = (SequenceIndex, Vec<usize>)> {
|
||||||
|
let pos = |start, stop, step| SequenceIndex::PosRange { start, stop, step };
|
||||||
|
let neg = |start, stop, step| SequenceIndex::NegRange { start, stop, step };
|
||||||
|
|
||||||
|
[
|
||||||
|
(SequenceIndex::Int(3), vec![3]),
|
||||||
|
(pos(0, 5, 2), vec![0, 2, 4]),
|
||||||
|
(pos(2, 10, 1), vec![2, 3, 4, 5, 6, 7, 8, 9]),
|
||||||
|
(pos(1, 15, 3), vec![1, 4, 7, 10, 13]),
|
||||||
|
(neg(Some(3), None, 1), vec![3, 2, 1, 0]),
|
||||||
|
(neg(Some(3), None, 2), vec![3, 1]),
|
||||||
|
(neg(Some(2), Some(0), 1), vec![2, 1]),
|
||||||
|
(neg(Some(2), Some(0), 2), vec![2]),
|
||||||
|
(neg(Some(2), Some(0), 3), vec![2]),
|
||||||
|
(neg(Some(10), Some(2), 3), vec![10, 7, 4]),
|
||||||
|
(neg(None, None, 1), vec![]),
|
||||||
|
(neg(None, None, 3), vec![]),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that the index iterator's implementation of `ExactSizeIterator` is correct.
|
||||||
|
#[test]
|
||||||
|
fn index_iterator() {
|
||||||
|
for (index, forwards) in index_iterator_cases() {
|
||||||
|
// We're testing that all the values are the same, and the `size_hint` is correct at
|
||||||
|
// every single point.
|
||||||
|
let mut actual = Vec::new();
|
||||||
|
let mut sizes = Vec::new();
|
||||||
|
let mut iter = index.iter();
|
||||||
|
loop {
|
||||||
|
sizes.push(iter.size_hint().0);
|
||||||
|
if let Some(next) = iter.next() {
|
||||||
|
actual.push(next);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
actual, forwards,
|
||||||
|
"values for {:?}\nActual : {:?}\nExpected: {:?}",
|
||||||
|
index, actual, forwards,
|
||||||
|
);
|
||||||
|
let expected_sizes = (0..=forwards.len()).rev().collect::<Vec<_>>();
|
||||||
|
assert_eq!(
|
||||||
|
sizes, expected_sizes,
|
||||||
|
"sizes for {:?}\nActual : {:?}\nExpected: {:?}",
|
||||||
|
index, sizes, expected_sizes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that the index iterator's implementation of `DoubleEndedIterator` is correct.
|
||||||
|
#[test]
|
||||||
|
fn reversed_index_iterator() {
|
||||||
|
for (index, forwards) in index_iterator_cases() {
|
||||||
|
let actual = index.iter().rev().collect::<Vec<_>>();
|
||||||
|
let expected = forwards.into_iter().rev().collect::<Vec<_>>();
|
||||||
|
assert_eq!(
|
||||||
|
actual, expected,
|
||||||
|
"reversed {:?}\nActual : {:?}\nExpected: {:?}",
|
||||||
|
index, actual, expected,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that `descending` produces its values in reverse-sorted order.
|
||||||
|
#[test]
|
||||||
|
fn descending() {
|
||||||
|
for (index, mut expected) in index_iterator_cases() {
|
||||||
|
let actual = index.descending().collect::<Vec<_>>();
|
||||||
|
expected.sort_by(|left, right| right.cmp(left));
|
||||||
|
assert_eq!(
|
||||||
|
actual, expected,
|
||||||
|
"descending {:?}\nActual : {:?}\nExpected: {:?}",
|
||||||
|
index, actual, expected,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue