use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::radix::Radix;
use crate::{CompactStorage, CompactVec, QuditSystem};
#[derive(Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Radices(CompactVec<Radix>);
impl Radices {
pub fn new<T: Into<Radices>>(radices: T) -> Self {
radices.into()
}
pub fn guess(dimension: usize) -> Radices {
if dimension < 2 {
panic!("Invalid dimension in Radices");
}
if dimension & (dimension - 1) == 0 {
let num_qudits = dimension.trailing_zeros() as usize;
return vec![2; num_qudits].into();
}
let mut n = dimension;
let mut power = 0;
while n > 1 {
if !n.is_multiple_of(3) {
return [dimension].into();
}
n /= 3;
power += 1;
}
vec![3; power].into()
}
#[track_caller]
pub fn expand(&self, mut index: usize) -> Vec<usize> {
if index >= self.dimension() {
panic!(
"Provided index {} is too large for this system with radices: {:#?}",
index, self
);
}
let mut expansion = vec![0; self.num_qudits()];
for (idx, radix) in self.iter().enumerate().rev() {
let casted_radix: usize = (*radix).into();
let coef: usize = index % casted_radix;
index -= coef;
index /= casted_radix;
expansion[idx] = coef;
}
expansion
}
#[track_caller]
pub fn compress(&self, expansion: &[usize]) -> usize {
if self.len() != expansion.len() {
panic!("Invalid expansion: incorrect number of qudits.")
}
if expansion
.iter()
.enumerate()
.any(|(index, coef)| *coef >= usize::from(self[index]))
{
panic!("Invalid expansion: mismatch in qudit radices.")
}
if expansion.is_empty() {
return 0;
}
let mut acm_val = expansion[self.num_qudits() - 1];
let mut acm_base = usize::from(self[self.num_qudits() - 1]);
for coef_index in (0..expansion.len() - 1).rev() {
let coef = expansion[coef_index];
acm_val += coef * acm_base;
acm_base *= usize::from(self[coef_index]);
}
acm_val
}
pub fn place_values(&self) -> Vec<usize> {
let mut place_values = vec![0; self.num_qudits()];
let mut acm = 1;
for (idx, r) in self.iter().enumerate().rev() {
place_values[idx] = acm;
acm *= usize::from(*r);
}
place_values
}
#[inline(always)]
pub fn concat(&self, other: &Radices) -> Radices {
self.iter().chain(other.iter()).copied().collect()
}
pub fn counts(&self) -> HashMap<Radix, usize> {
let mut counts = HashMap::new();
for radix in self.iter() {
*counts.entry(*radix).or_insert(0) += 1;
}
counts
}
pub fn len(&self) -> usize {
self.num_qudits()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl crate::QuditSystem for Radices {
#[inline(always)]
fn radices(&self) -> Radices {
self.clone()
}
#[inline(always)]
fn dimension(&self) -> usize {
self.iter().product::<usize>()
}
#[inline(always)]
fn num_qudits(&self) -> usize {
self.0.len()
}
#[inline(always)]
fn is_qubit_only(&self) -> bool {
self.iter().all(|r| *r == 2)
}
#[inline(always)]
fn is_qutrit_only(&self) -> bool {
self.iter().all(|r| *r == 3)
}
#[inline(always)]
fn is_qudit_only<T: Into<Radix>>(&self, radix: T) -> bool {
let radix = radix.into();
self.iter().all(|r| *r == radix)
}
#[inline(always)]
fn is_homogenous(&self) -> bool {
self.is_qudit_only(self[0])
}
}
impl<T: Into<Radix>> core::iter::FromIterator<T> for Radices {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let vec: CompactVec<Radix> = iter.into_iter().map(|r| r.into()).collect();
Radices(vec)
}
}
impl core::ops::Deref for Radices {
type Target = [Radix];
#[inline(always)]
fn deref(&self) -> &[Radix] {
unsafe {
std::mem::transmute(
std::mem::transmute::<&CompactVec<Radix>, &CompactVec<u8>>(&self.0).deref(),
)
}
}
}
impl<T: Into<Radix> + CompactStorage> From<CompactVec<T>> for Radices {
#[inline(always)]
fn from(value: CompactVec<T>) -> Self {
value.into_iter().map(|x| x.into()).collect()
}
}
impl<T: Into<Radix> + 'static> From<Vec<T>> for Radices {
#[inline(always)]
fn from(value: Vec<T>) -> Self {
let vec = if std::any::TypeId::of::<T>() == std::any::TypeId::of::<Radix>() {
CompactVec::<Radix>::from(unsafe { std::mem::transmute::<Vec<T>, Vec<Radix>>(value) })
} else {
value.into_iter().map(|x| x.into()).collect()
};
Radices(vec)
}
}
impl<T: Into<Radix> + Copy> From<&[T]> for Radices {
#[inline(always)]
fn from(value: &[T]) -> Self {
value.iter().copied().collect()
}
}
impl<T: Into<Radix>, const N: usize> From<[T; N]> for Radices {
#[inline(always)]
fn from(value: [T; N]) -> Self {
value.into_iter().collect()
}
}
impl<'a, T: Into<Radix> + Copy, const N: usize> From<&'a [T; N]> for Radices {
#[inline(always)]
fn from(value: &'a [T; N]) -> Self {
value.iter().copied().collect()
}
}
impl From<Radix> for Radices {
fn from(value: Radix) -> Self {
[value].into()
}
}
impl core::fmt::Debug for Radices {
#[inline(always)]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
<Radices as core::fmt::Display>::fmt(self, f)
}
}
impl core::fmt::Display for Radices {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[")?;
let enum_iter = self.iter().enumerate();
for (i, r) in enum_iter {
if i == self.len() - 1 {
write!(f, "{}", r)?;
} else {
write!(f, "{}, ", r)?;
}
}
write!(f, "]")?;
Ok(())
}
}
#[cfg(test)]
pub mod strategies {
use proptest::prelude::*;
use super::*;
impl Arbitrary for Radices {
type Parameters = (core::ops::Range<u8>, core::ops::Range<usize>);
type Strategy = BoxedStrategy<Self>;
fn arbitrary() -> Self::Strategy {
Self::arbitrary_with((2..5u8, 1..5))
}
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
prop::collection::vec(args.0, args.1)
.prop_map(Radices::new)
.boxed()
}
}
}
#[cfg(feature = "python")]
mod python {
use super::*;
use pyo3::{exceptions::PyTypeError, prelude::*};
use pyo3_stub_gen::impl_stub_type;
impl_stub_type!(Radices = usize | Vec<usize>);
impl<'a, 'py> FromPyObject<'a, 'py> for Radices {
type Error = PyErr;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
if let Ok(num) = ob.extract::<usize>() {
Ok(Radices::new([num]))
} else if let Ok(nums) = ob.extract::<Vec<usize>>() {
Ok(Radices::new(nums))
} else {
Err(PyTypeError::new_err(
"Expected a list of integers for radices.",
))
}
}
}
impl<'py> IntoPyObject<'py> for Radices {
type Target = pyo3::types::PyList;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let ints: Vec<usize> = self.iter().map(|&r| usize::from(r)).collect();
pyo3::types::PyList::new(py, ints)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn test_expand_compress(radices in any::<Radices>()) {
for index in 0..radices.dimension() {
let exp = radices.expand(index);
assert_eq!(radices.compress(&exp), index);
}
}
}
#[test]
fn test_slice_ops() {
let rdx = Radices::new(vec![2, 3, 4usize]);
assert_eq!(rdx.len(), 3);
assert_eq!(rdx[1], 3);
assert_eq!(rdx[1..], [3, 4]);
assert_eq!(rdx.clone(), rdx);
}
}