qudit_core/
radices.rs

1// TODO: remove all mentions of the old macro radices!
2// TODO: Order mods: implementations; python; strategies; test
3use std::collections::HashMap;
4
5use crate::radix::Radix;
6use crate::{CompactStorage, CompactVec, QuditSystem};
7
8/// The number of basis states for each qudit (or dit) in a quantum system.
9///
10/// This object represents the radix -- sometimes called the base, level
11/// or ditness -- of each qudit in a qudit system or dit in a classical system,
12/// and is implemented as a sequence of unsigned, byte-sized integers.
13/// A qubit is a two-level qudit or a qudit with radix two, while a
14/// qutrit is a three-level qudit. Two qutrits together are represented
15/// by the [3, 3] radices object.
16///
17/// No radix can be less than 2, as this would not be a valid.
18/// As each radix is represented by a byte, this implementation does not
19/// support individual radices greater than 255.
20///
21/// ## Ordering and Endianness
22///
23/// Indices are counted left to right, for example a [2, 3] qudit
24/// radices is interpreted as a qubit as the first qudit and a qutrit as
25/// the second one. OpenQudit uses big-endian ordering, so the qubit in
26/// the previous example is the most significant qudit and the qutrit is
27/// the least significant qudit. For example, in the same system, a state
28/// |10> would be represented by the decimal number 3.
29#[derive(Hash, PartialEq, Eq, Clone)]
30pub struct Radices(CompactVec<Radix>);
31
32impl Radices {
33    /// Constructs a Radices object from the given input.
34    ///
35    /// # Arguments
36    ///
37    /// * `radices` - A slice detailing the radices of a qudit system.
38    ///
39    /// # Panics
40    ///
41    /// If radices does not represent a valid system. This can happen
42    /// if any of the radices are 0 or 1.
43    ///
44    /// # Examples
45    ///
46    /// ```
47    /// # use qudit_core::Radices;
48    /// let three_qubits = Radices::new([2; 3]);
49    /// let qubit_qutrit = Radices::new([2, 3]);
50    /// ```
51    pub fn new<T: Into<Radices>>(radices: T) -> Self {
52        radices.into()
53    }
54
55    /// Attempts to determine the radices for a qudit system with given dimension.
56    ///
57    /// It first tries powers of two, failing that powers of three. If neither
58    /// then it will return a QuditRadices with only one qudit of dimension
59    /// equal to the input.
60    pub fn guess(dimension: usize) -> Radices {
61        if dimension < 2 {
62            panic!("Invalid dimension in Radices");
63        }
64
65        // Is a power of two?
66        if dimension & (dimension - 1) == 0 {
67            let num_qudits = dimension.trailing_zeros() as usize;
68            return vec![2; num_qudits].into();
69        }
70
71        let mut n = dimension;
72        let mut power = 0;
73        while n > 1 {
74            if !n.is_multiple_of(3) {
75                return [dimension].into();
76            }
77            n /= 3;
78            power += 1;
79        }
80        vec![3; power].into()
81    }
82
83    /// Construct the expanded form of an index in this numbering system.
84    ///
85    /// # Arguments
86    ///
87    /// * `index` - The number to expand.
88    ///
89    /// # Returns
90    ///
91    /// A vector of coefficients for each qudit in the system. Note that
92    /// the coefficients are in big-endian order, that is, the first
93    /// coefficient is for the most significant qudit.
94    ///
95    /// # Panics
96    ///
97    /// If `index` is too large for this system, that is, if it is greater
98    /// than the product of the radices.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// # use qudit_core::Radices;
104    ///
105    /// let hybrid_system = Radices::new([2, 3]);
106    /// assert_eq!(hybrid_system.expand(3), vec![1, 0]);
107    ///
108    /// let four_qubits = Radices::new([2, 2, 2, 2]);
109    /// assert_eq!(four_qubits.expand(7), vec![0, 1, 1, 1]);
110    ///
111    /// let two_qutrits = Radices::new([3, 3]);
112    /// assert_eq!(two_qutrits.expand(7), vec![2, 1]);
113    ///
114    /// let hybrid_system = Radices::new([3, 2, 3]);
115    /// assert_eq!(hybrid_system.expand(17), vec![2, 1, 2]);
116    /// ```
117    ///
118    /// # See Also
119    ///
120    /// * [Self::compress] - The inverse of this function.
121    /// * [Self::place_values] - The place values for each position in the expansion.
122    #[track_caller]
123    pub fn expand(&self, mut index: usize) -> Vec<usize> {
124        if index >= self.dimension() {
125            panic!(
126                "Provided index {} is too large for this system with radices: {:#?}",
127                index, self
128            );
129        }
130
131        let mut expansion = vec![0; self.num_qudits()];
132
133        for (idx, radix) in self.iter().enumerate().rev() {
134            let casted_radix: usize = (*radix).into();
135            let coef: usize = index % casted_radix;
136            index -= coef;
137            index /= casted_radix;
138            expansion[idx] = coef;
139        }
140
141        expansion
142    }
143
144    /// Destruct an expanded form of an index back into its base 10 number.
145    ///
146    /// # Arguments
147    ///
148    /// * `expansion` - The expansion to compress.
149    ///
150    /// # Panics
151    ///
152    /// If `expansion` has a mismatch in length or radices.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// # use qudit_core::Radices;
158    ///
159    /// let hybrid_system = Radices::new([2, 3]);
160    /// assert_eq!(hybrid_system.compress(&vec![1, 0]), 3);
161    ///
162    /// let four_qubits = Radices::new([2, 2, 2, 2]);
163    /// assert_eq!(four_qubits.compress(&vec![0, 1, 1, 1]), 7);
164    ///
165    /// let two_qutrits = Radices::new([3, 3]);
166    /// assert_eq!(two_qutrits.compress(&vec![2, 1]), 7);
167    ///
168    /// let hybrid_system = Radices::new([3, 2, 3]);
169    /// assert_eq!(hybrid_system.compress(&vec![2, 1, 2]), 17);
170    /// ```
171    ///
172    /// # See Also
173    ///
174    /// * [Self::expand] - The inverse of this function.
175    /// * [Self::place_values] - The place values for each position in the expansion.
176    #[track_caller]
177    pub fn compress(&self, expansion: &[usize]) -> usize {
178        if self.len() != expansion.len() {
179            panic!("Invalid expansion: incorrect number of qudits.")
180        }
181
182        if expansion
183            .iter()
184            .enumerate()
185            .any(|(index, coef)| *coef >= usize::from(self[index]))
186        {
187            panic!("Invalid expansion: mismatch in qudit radices.")
188        }
189
190        if expansion.is_empty() {
191            return 0;
192        }
193
194        let mut acm_val = expansion[self.num_qudits() - 1];
195        let mut acm_base = usize::from(self[self.num_qudits() - 1]);
196
197        for coef_index in (0..expansion.len() - 1).rev() {
198            let coef = expansion[coef_index];
199            acm_val += coef * acm_base;
200            acm_base *= usize::from(self[coef_index]);
201        }
202
203        acm_val
204    }
205
206    /// Calculate the value for each expansion position in this numbering system.
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// # use qudit_core::Radices;
212    ///
213    /// let two_qubits = Radices::new([2, 2]);
214    /// assert_eq!(two_qubits.place_values(), vec![2, 1]);
215    ///
216    /// let two_qutrits = Radices::new([3, 3]);
217    /// assert_eq!(two_qutrits.place_values(), vec![3, 1]);
218    ///
219    /// let hybrid_system = Radices::new([3, 2, 3]);
220    /// assert_eq!(hybrid_system.place_values(), vec![6, 3, 1]);
221    /// ```
222    ///
223    /// # See Also
224    /// * [Self::expand] - Expand an decimal value into this numbering system.
225    /// * [Self::compress] - Compress an expansion in this numbering system to decimal.
226    pub fn place_values(&self) -> Vec<usize> {
227        let mut place_values = vec![0; self.num_qudits()];
228        let mut acm = 1;
229        for (idx, r) in self.iter().enumerate().rev() {
230            place_values[idx] = acm;
231            acm *= usize::from(*r);
232        }
233        place_values
234    }
235
236    /// Concatenates two QuditRadices objects into a new object.
237    ///
238    /// # Arguments
239    ///
240    /// * `other` - The other QuditRadices object to concatenate with `self`.
241    ///
242    /// # Examples
243    ///
244    /// ```
245    /// # use qudit_core::Radices;
246    ///
247    /// let two_qubits = Radices::new([2, 2]);
248    /// let two_qutrits = Radices::new([3, 3]);
249    /// let four_qudits = Radices::new([2, 2, 3, 3]);
250    /// assert_eq!(two_qubits.concat(&two_qutrits), four_qudits);
251    ///
252    /// let hybrid_system = Radices::new([3, 2, 3]);
253    /// let two_qutrits = Radices::new([3, 3]);
254    /// let five_qudits = Radices::new([3, 2, 3, 3, 3]);
255    /// assert_eq!(hybrid_system.concat(&two_qutrits), five_qudits);
256    /// ```
257    #[inline(always)]
258    pub fn concat(&self, other: &Radices) -> Radices {
259        self.iter().chain(other.iter()).copied().collect()
260    }
261
262    /// Returns the number of each radix in the system.
263    ///
264    /// # Examples
265    ///
266    /// ```
267    /// # use qudit_core::Radices;
268    /// # use std::collections::HashMap;
269    /// let two_qubits = Radices::new([2, 2]);
270    /// let counts = two_qubits.counts();
271    /// assert_eq!(counts.get(&(2.into())), Some(&2));
272    ///
273    /// let two_qutrits = Radices::new([3, 3]);
274    /// let counts = two_qutrits.counts();
275    /// assert_eq!(counts.get(&(3.into())), Some(&2));
276    ///
277    /// let hybrid_system = Radices::new([3, 2, 3]);
278    /// let counts = hybrid_system.counts();
279    /// assert_eq!(counts.get(&(3.into())), Some(&2));
280    /// assert_eq!(counts.get(&(2.into())), Some(&1));
281    /// ```
282    pub fn counts(&self) -> HashMap<Radix, usize> {
283        let mut counts = HashMap::new();
284        for radix in self.iter() {
285            *counts.entry(*radix).or_insert(0) += 1;
286        }
287        counts
288    }
289
290    /// Returns the length of the radices object.
291    pub fn len(&self) -> usize {
292        self.num_qudits()
293    }
294
295    /// Returns true is the system is empty, false otherwise.
296    pub fn is_empty(&self) -> bool {
297        self.len() == 0
298    }
299}
300
301impl crate::QuditSystem for Radices {
302    /// Returns the radices of the system.
303    ///
304    /// See [`QuditSystem`] for more information.
305    #[inline(always)]
306    fn radices(&self) -> Radices {
307        self.clone()
308    }
309
310    /// Returns the dimension of a system described by these radices.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// # use qudit_core::Radices;
316    /// # use qudit_core::QuditSystem;
317    ///
318    /// let two_qubits = Radices::new([2, 2]);
319    /// assert_eq!(two_qubits.dimension(), 4);
320    ///
321    /// let two_qutrits = Radices::new([3, 3]);
322    /// assert_eq!(two_qutrits.dimension(), 9);
323    ///
324    /// let hybrid_system = Radices::new([3, 2, 3]);
325    /// assert_eq!(hybrid_system.dimension(), 18);
326    /// ```
327    #[inline(always)]
328    fn dimension(&self) -> usize {
329        self.iter().product::<usize>()
330    }
331
332    /// Returns the number of qudits represented by these radices.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// # use qudit_core::Radices;
338    /// # use qudit_core::QuditSystem;
339    ///
340    /// let two_qubits = Radices::new([2, 2]);
341    /// assert_eq!(two_qubits.num_qudits(), 2);
342    ///
343    /// let two_qutrits = Radices::new([3, 3]);
344    /// assert_eq!(two_qutrits.num_qudits(), 2);
345    ///
346    /// let hybrid_system = Radices::new([3, 2, 3]);
347    /// assert_eq!(hybrid_system.num_qudits(), 3);
348    ///
349    /// let ten_qubits = Radices::new(vec![2; 10]);
350    /// assert_eq!(ten_qubits.num_qudits(), 10);
351    /// ```
352    #[inline(always)]
353    fn num_qudits(&self) -> usize {
354        self.0.len()
355    }
356
357    /// Returns true if these radices describe a qubit-only system.
358    ///
359    /// # Examples
360    ///
361    /// ```
362    /// # use qudit_core::Radices;
363    /// # use qudit_core::QuditSystem;
364    ///
365    /// let two_qubits = Radices::new([2, 2]);
366    /// assert!(two_qubits.is_qubit_only());
367    ///
368    /// let qubit_qutrit = Radices::new([2, 3]);
369    /// assert!(!qubit_qutrit.is_qubit_only());
370    ///
371    /// let two_qutrits = Radices::new([3, 3]);
372    /// assert!(!two_qutrits.is_qubit_only());
373    /// ```
374    #[inline(always)]
375    fn is_qubit_only(&self) -> bool {
376        self.iter().all(|r| *r == 2)
377    }
378
379    /// Returns true if these radices describe a qutrit-only system.
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// # use qudit_core::Radices;
385    /// # use qudit_core::QuditSystem;
386    ///
387    /// let two_qubits = Radices::new([2, 2]);
388    /// assert!(!two_qubits.is_qutrit_only());
389    ///
390    /// let qubit_qutrit = Radices::new([2, 3]);
391    /// assert!(!qubit_qutrit.is_qutrit_only());
392    ///
393    /// let two_qutrits = Radices::new([3, 3]);
394    /// assert!(two_qutrits.is_qutrit_only());
395    /// ```
396    #[inline(always)]
397    fn is_qutrit_only(&self) -> bool {
398        self.iter().all(|r| *r == 3)
399    }
400
401    /// Returns true if these radices describe a `radix`-only system.
402    ///
403    /// # Arguments
404    ///
405    /// * `radix` - The radix to check for.
406    ///
407    /// # Examples
408    ///
409    /// ```
410    /// # use qudit_core::Radices;
411    /// # use qudit_core::QuditSystem;
412    ///
413    /// let two_qubits = Radices::new([2, 2]);
414    /// assert!(two_qubits.is_qudit_only(2));
415    /// assert!(!two_qubits.is_qudit_only(3));
416    ///
417    /// let mixed_qudits = Radices::new([2, 3]);
418    /// assert!(!mixed_qudits.is_qudit_only(2));
419    /// assert!(!mixed_qudits.is_qudit_only(3));
420    /// ```
421    #[inline(always)]
422    fn is_qudit_only<T: Into<Radix>>(&self, radix: T) -> bool {
423        let radix = radix.into();
424        self.iter().all(|r| *r == radix)
425    }
426
427    /// Returns true if these radices describe a homogenous system.
428    ///
429    /// # Examples
430    ///
431    /// ```
432    /// # use qudit_core::Radices;
433    /// # use qudit_core::QuditSystem;
434    ///
435    /// let two_qubits = Radices::new([2, 2]);
436    /// assert!(two_qubits.is_homogenous());
437    ///
438    /// let mixed_qudits = Radices::new([2, 3]);
439    /// assert!(!mixed_qudits.is_homogenous());
440    /// ```
441    #[inline(always)]
442    fn is_homogenous(&self) -> bool {
443        self.is_qudit_only(self[0])
444    }
445}
446
447impl<T: Into<Radix>> core::iter::FromIterator<T> for Radices {
448    /// Creates a new QuditRadices object from an iterator.
449    ///
450    /// # Arguments
451    ///
452    /// * `iter` - An iterator over the radices of a qudit system.
453    ///
454    /// # Panics
455    ///
456    /// If radices does not represent a valid system. This can happen
457    /// if any of the radices are 0 or 1.
458    ///
459    /// # Examples
460    ///
461    /// ```
462    /// # use qudit_core::Radices;
463    ///
464    /// let qubit_qutrit = Radices::from_iter(2..4);
465    /// assert_eq!(qubit_qutrit, Radices::new([2, 3]));
466    ///
467    /// let two_qutrits = Radices::from_iter(vec![3, 3]);
468    /// assert_eq!(two_qutrits, Radices::new([3, 3]));
469    ///
470    /// // Ten qubits then ten qutrits
471    /// let mixed_system = Radices::from_iter(vec![2; 10].into_iter()
472    ///                         .chain(vec![3; 10].into_iter()));
473    ///
474    /// // Using .collect()
475    /// let from_collect: Radices = (2..5).collect();
476    /// assert_eq!(from_collect, Radices::new([2, 3, 4]));
477    /// ```
478    ///
479    /// # Note
480    ///
481    /// This will attempt to avoid an allocation when possible.
482    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
483        let vec: CompactVec<Radix> = iter.into_iter().map(|r| r.into()).collect();
484        Radices(vec)
485    }
486}
487
488impl core::ops::Deref for Radices {
489    type Target = [Radix];
490
491    #[inline(always)]
492    fn deref(&self) -> &[Radix] {
493        // Safety Radix is a transparent wrapper around u8
494        unsafe {
495            std::mem::transmute(
496                std::mem::transmute::<&CompactVec<Radix>, &CompactVec<u8>>(&self.0).deref(),
497            )
498        }
499    }
500}
501
502impl<T: Into<Radix> + CompactStorage> From<CompactVec<T>> for Radices {
503    #[inline(always)]
504    fn from(value: CompactVec<T>) -> Self {
505        value.into_iter().map(|x| x.into()).collect()
506    }
507}
508
509impl<T: Into<Radix> + 'static> From<Vec<T>> for Radices {
510    #[inline(always)]
511    fn from(value: Vec<T>) -> Self {
512        let vec = if std::any::TypeId::of::<T>() == std::any::TypeId::of::<Radix>() {
513            // If T is radix, then doing CompactVec::from will provide move semantics efficiently
514            // Safety: T == Radix, so Vec<T> == Vec<Radix>
515            CompactVec::<Radix>::from(unsafe { std::mem::transmute::<Vec<T>, Vec<Radix>>(value) })
516        } else {
517            value.into_iter().map(|x| x.into()).collect()
518        };
519
520        Radices(vec)
521    }
522}
523
524impl<T: Into<Radix> + Copy> From<&[T]> for Radices {
525    #[inline(always)]
526    fn from(value: &[T]) -> Self {
527        value.iter().copied().collect()
528    }
529}
530
531impl<T: Into<Radix>, const N: usize> From<[T; N]> for Radices {
532    #[inline(always)]
533    fn from(value: [T; N]) -> Self {
534        value.into_iter().collect()
535    }
536}
537
538impl<'a, T: Into<Radix> + Copy, const N: usize> From<&'a [T; N]> for Radices {
539    #[inline(always)]
540    fn from(value: &'a [T; N]) -> Self {
541        value.iter().copied().collect()
542    }
543}
544
545impl From<Radix> for Radices {
546    fn from(value: Radix) -> Self {
547        [value].into()
548    }
549}
550
551impl core::fmt::Debug for Radices {
552    /// Formats the radices as a string.
553    ///
554    /// See Display for more information.
555    #[inline(always)]
556    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
557        <Radices as core::fmt::Display>::fmt(self, f)
558    }
559}
560
561impl core::fmt::Display for Radices {
562    /// Formats the radices as a string.
563    ///
564    /// # Examples
565    ///
566    /// ```
567    /// # use qudit_core::Radices;
568    /// let two_qubits = Radices::new([2, 2]);
569    /// let two_qutrits = Radices::new([3, 3]);
570    /// let qubit_qutrit = Radices::new([2, 3]);
571    ///
572    /// assert_eq!(format!("{}", two_qubits), "[2, 2]");
573    /// assert_eq!(format!("{}", two_qutrits), "[3, 3]");
574    /// assert_eq!(format!("{}", qubit_qutrit), "[2, 3]");
575    /// ```
576    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
577        write!(f, "[")?;
578        let enum_iter = self.iter().enumerate();
579        for (i, r) in enum_iter {
580            if i == self.len() - 1 {
581                write!(f, "{}", r)?;
582            } else {
583                write!(f, "{}, ", r)?;
584            }
585        }
586
587        write!(f, "]")?;
588        Ok(())
589    }
590}
591
592#[cfg(test)]
593pub mod strategies {
594    use proptest::prelude::*;
595
596    use super::*;
597
598    impl Arbitrary for Radices {
599        type Parameters = (core::ops::Range<u8>, core::ops::Range<usize>);
600        type Strategy = BoxedStrategy<Self>;
601
602        /// Generate a random Radices object.
603        ///
604        /// By default, the number of radices is chosen randomly between
605        /// 1 and 4, and the radices themselves are chosen randomly
606        /// between 2 and 4.
607        fn arbitrary() -> Self::Strategy {
608            Self::arbitrary_with((2..5u8, 1..5))
609        }
610
611        /// Generate a random Radices object with the given parameters.
612        ///
613        /// # Arguments
614        ///
615        /// * `args` - A tuple of ranges for the number of radices and the
616        ///   radices themselves. The first range is for the number
617        ///   of radices, and the second range is for the radices
618        ///   themselves.
619        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
620            prop::collection::vec(args.0, args.1)
621                .prop_map(Radices::new)
622                .boxed()
623        }
624    }
625}
626
627#[cfg(feature = "python")]
628mod python {
629    use super::*;
630    use pyo3::{exceptions::PyTypeError, prelude::*};
631
632    impl<'a, 'py> FromPyObject<'a, 'py> for Radices {
633        type Error = PyErr;
634
635        fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
636            if let Ok(num) = ob.extract::<usize>() {
637                Ok(Radices::new([num]))
638            } else if let Ok(nums) = ob.extract::<Vec<usize>>() {
639                Ok(Radices::new(nums))
640            } else {
641                Err(PyTypeError::new_err(
642                    "Expected a list of integers for radices.",
643                ))
644            }
645        }
646    }
647}
648
649#[cfg(test)]
650mod tests {
651    use super::*;
652    use proptest::prelude::*;
653
654    proptest! {
655        /// An expansion should compress to the same value.
656        #[test]
657        fn test_expand_compress(radices in any::<Radices>()) {
658            for index in 0..radices.dimension() {
659                let exp = radices.expand(index);
660                assert_eq!(radices.compress(&exp), index);
661            }
662        }
663    }
664
665    #[test]
666    fn test_slice_ops() {
667        let rdx = Radices::new(vec![2, 3, 4usize]);
668        assert_eq!(rdx.len(), 3);
669        assert_eq!(rdx[1], 3);
670        assert_eq!(rdx[1..], [3, 4]);
671        assert_eq!(rdx.clone(), rdx);
672    }
673}