use num_complex::Complex64;
use numpy::{IntoPyArray as _, PyArray1};
use pyo3::{
exceptions::{PyIndexError, PyStopIteration, PyValueError},
prelude::*,
types::{PySlice, PySliceIndices},
};
#[cfg(feature = "stubs")]
use pyo3_stub_gen::{
derive::{
gen_methods_from_python, gen_stub_pyclass, gen_stub_pyclass_complex_enum,
gen_stub_pymethods,
},
PyStubType, TypeInfo,
};
use crate::waveform::sampling::*;
pub(crate) fn register_abcs<'py>(py: Python<'py>) -> PyResult<()> {
pyo3::types::PySequence::register::<PyIqSamples>(py)?;
Ok(())
}
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "stubs", gen_stub_pyclass_complex_enum)]
#[pyclass(
module = "quil._quil.waveform.sampling",
name = "IqSamples",
eq,
frozen,
from_py_object
)]
pub enum PyIqSamples {
Flat { iq: Complex64, sample_count: usize },
Samples { samples: Vec<Complex64> },
}
#[derive(Clone, Debug, derive_more::From, derive_more::Into)]
#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
#[pyclass(
module = "quil._quil.waveform.sampling",
name = "IqSamplesIter",
from_py_object
)]
pub struct PyIqSamplesIter(pub IntoIter<Complex64>);
#[derive(Clone, Debug, derive_more::From, derive_more::Into)]
#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
#[pyclass(
module = "quil._quil.waveform.sampling",
name = "IqSamplesRevIter",
from_py_object
)]
pub struct PyIqSamplesRevIter(pub std::iter::Rev<IntoIter<Complex64>>);
impl From<IqSamples<Complex64>> for PyIqSamples {
fn from(value: IqSamples<Complex64>) -> Self {
match value {
IqSamples::Flat { iq, sample_count } => Self::Flat { iq, sample_count },
IqSamples::Samples(samples) => Self::Samples { samples },
}
}
}
impl From<PyIqSamples> for IqSamples<Complex64> {
fn from(value: PyIqSamples) -> Self {
match value {
PyIqSamples::Flat { iq, sample_count } => Self::Flat { iq, sample_count },
PyIqSamples::Samples { samples } => Self::Samples(samples),
}
}
}
#[derive(Debug, FromPyObject)]
pub enum IqSamplesIndex<'py> {
Int(isize),
Slice(Bound<'py, PySlice>),
}
#[derive(Debug, IntoPyObject)]
pub enum IqSamplesIndexed {
One(Complex64),
Many(Vec<Complex64>),
}
#[cfg(feature = "stubs")]
impl PyStubType for IqSamplesIndex<'_> {
fn type_output() -> TypeInfo {
isize::type_output() | TypeInfo::builtin("slice[int | None]")
}
}
#[cfg(feature = "stubs")]
impl PyStubType for IqSamplesIndexed {
fn type_output() -> TypeInfo {
Complex64::type_output() | TypeInfo::list_of::<Complex64>()
}
}
#[cfg(feature = "stubs")]
pyo3::inventory::submit! {
gen_methods_from_python! {r#"
class PyIqSamples:
@overload
def get(self, index: int) -> typing.Optional[complex]: ...
@overload
def get(self, index: slice[typing.Optional[int]]) -> list[complex]: ...
@overload
def __getitem__(self, index: int) -> complex: ...
@overload
def __getitem__(self, index: slice[typing.Optional[int]]) -> list[complex]: ...
"#}
}
#[cfg_attr(not(feature = "stubs"), optipy::strip_pyo3(only_stubs))]
#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
#[pymethods]
impl PyIqSamples {
#[getter(sample_count)]
pub fn sample_count(&self) -> usize {
match self {
Self::Flat {
sample_count,
iq: _,
} => *sample_count,
Self::Samples { samples } => samples.len(),
}
}
#[pyo3(name = "__len__")]
pub fn len(&self) -> usize {
self.sample_count()
}
#[pyo3(name = "__length_hint__")]
pub fn length_hint(&self) -> usize {
self.sample_count()
}
#[pyo3(name = "__iter__")]
pub fn iter(&self) -> PyIqSamplesIter {
PyIqSamplesIter(IqSamples::from(self.clone()).into_iter())
}
#[pyo3(name = "__reversed__")]
pub fn reversed(&self) -> PyIqSamplesRevIter {
PyIqSamplesRevIter(self.iter().0.rev())
}
#[pyo3(name = "__contains__")]
pub fn contains(&self, value: Bound<'_, PyAny>) -> bool {
let Ok(value) = value.extract::<Complex64>() else {
return false;
};
match self {
Self::Flat { iq, sample_count } => *sample_count > 0 && *iq == value,
Self::Samples { samples } => samples.contains(&value),
}
}
#[pyo3(name = "get")]
pub fn get(&self, index: IqSamplesIndex<'_>) -> PyResult<Option<IqSamplesIndexed>> {
#[inline]
fn get_one(this: &PyIqSamples, mut index: isize) -> Option<Complex64> {
if index < 0 {
index = index.checked_add_unsigned(this.sample_count())?;
}
let index = usize::try_from(index).ok()?;
match this {
PyIqSamples::Flat { iq, sample_count } => (index < *sample_count).then_some(*iq),
PyIqSamples::Samples { samples } => samples.get(index).copied(),
}
}
match index {
IqSamplesIndex::Int(index) => Ok(get_one(self, index).map(IqSamplesIndexed::One)),
IqSamplesIndex::Slice(indices) => {
let sample_count = self.sample_count();
let Ok(signed_sample_count) = isize::try_from(sample_count) else {
return Err(PyIndexError::new_err(format!(
"cannot index into an IqSamples.Flat object with >= isize::MAX ({}) \
samples using a slice",
isize::MAX
)));
};
let PySliceIndices {
start,
stop,
step,
slicelength,
} = indices.indices(signed_sample_count)?;
debug_assert!(step != 0);
debug_assert!(slicelength <= sample_count);
debug_assert!(start >= 0 || (start == -1 && step < 0));
debug_assert!(stop >= 0 || (stop == -1 && step < 0));
let sliced_samples = match self {
Self::Flat {
iq,
sample_count: _,
} => vec![*iq; slicelength],
Self::Samples { samples } => {
if let Ok(step) = usize::try_from(step) {
let start = start as usize; let stop = stop as usize;
if step == 1 {
samples[start..stop].to_owned()
} else {
let mut result = Vec::with_capacity(slicelength);
let mut i = start;
while i < stop {
result.push(samples[i]);
i += step;
}
result
}
} else {
let mut result = Vec::with_capacity(slicelength);
let mut i = start;
while i > stop {
result.push(samples[i as usize]);
i += step; }
result
}
}
};
Ok(Some(IqSamplesIndexed::Many(sliced_samples)))
}
}
}
#[pyo3(name = "__getitem__")]
pub fn getitem(&self, index: IqSamplesIndex<'_>) -> PyResult<IqSamplesIndexed> {
self.get(index)?
.ok_or_else(|| PyIndexError::new_err("sample index out of range"))
}
pub fn count(&self, value: Bound<'_, PyAny>) -> usize {
let Ok(value) = value.extract::<Complex64>() else {
return 0;
};
match self {
Self::Flat { iq, sample_count } => {
if *iq == value {
*sample_count
} else {
0
}
}
Self::Samples { samples } => samples.iter().filter(|sample| **sample == value).count(),
}
}
#[pyo3(signature = (value, start = 0, stop = None))]
pub fn index(
&self,
value: Bound<'_, PyAny>,
start: isize,
stop: Option<isize>,
) -> PyResult<usize> {
let error = || PyValueError::new_err(format!("{value:?} is not in samples"));
let Ok(value) = value.extract::<Complex64>() else {
return Err(error());
};
let sample_count = self.sample_count();
let (start, stop) = if let Ok(signed_sample_count) = isize::try_from(sample_count) {
let signed_index = |i: isize| i.clamp(-signed_sample_count, signed_sample_count);
(signed_index(start), stop.map(signed_index))
} else {
(start, stop)
};
let unsigned_index =
|i: isize| usize::try_from(i).unwrap_or(sample_count.strict_add_signed(i));
let start = unsigned_index(start);
let stop = stop.map(unsigned_index).unwrap_or(sample_count);
if start >= stop {
return Err(error());
}
match self {
Self::Flat {
iq,
sample_count: _,
} => {
if *iq == value {
Ok(start)
} else {
Err(error())
}
}
Self::Samples { samples } => samples[start..stop]
.iter()
.position(|sample| *sample == value)
.map(|i| i + start)
.ok_or_else(error),
}
}
pub fn iq_values<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1<Complex64>> {
IqSamples::from(self.clone())
.into_iq_values()
.into_pyarray(py)
}
fn __repr__<'py>(&self, py: Python<'py>) -> PyResult<String> {
let complex_repr = |c: &Complex64| c.into_pyobject(py)?.repr();
match self {
Self::Flat { iq, sample_count } => Ok(format!(
"IqSamples.Flat(iq={iq}, sample_count={sample_count})",
iq = complex_repr(iq)?.to_str()?,
)),
Self::Samples { samples } => {
let mut output = "IqSamples.Samples([".to_owned();
let mut first = true;
for sample in samples {
if first {
first = false;
} else {
output.push_str(", ");
}
output.push_str(complex_repr(sample)?.to_str()?);
}
output.push_str("])");
Ok(output)
}
}
}
}
#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
#[pymethods]
impl PyIqSamplesIter {
#[pyo3(name = "__next__")]
pub fn next(&mut self) -> PyResult<Complex64> {
self.0.next().ok_or_else(|| PyStopIteration::new_err(()))
}
#[pyo3(name = "__iter__")]
pub fn iter(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __repr__(slf: PyRef<'_, Self>) -> String {
format!("<IqSamplesIter object at {:?}>", slf.as_ptr())
}
}
#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
#[pymethods]
impl PyIqSamplesRevIter {
#[pyo3(name = "__next__")]
pub fn next(&mut self) -> PyResult<Complex64> {
self.0.next().ok_or_else(|| PyStopIteration::new_err(()))
}
#[pyo3(name = "__iter__")]
pub fn iter(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __repr__(slf: PyRef<'_, Self>) -> String {
format!("<IqSamplesRevIter object at {:?}>", slf.as_ptr())
}
}