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