qsym2/chartab/
unityroot.rs

1//! Symbolic representation of roots of unity for characters.
2
3use std::fmt;
4use std::ops::Mul;
5
6use derive_builder::Builder;
7use fraction::{self, ToPrimitive};
8use num::Complex;
9use num_traits::Pow;
10use serde::{Deserialize, Serialize};
11
12type F = fraction::GenericFraction<u32>;
13
14#[cfg(test)]
15#[path = "unityroot_tests.rs"]
16mod unityroot_tests;
17
18/// Structure to represent roots of unity symbolically.
19///
20/// Partial orders between roots of unity are based on their angular positions
21/// on the unit circle in the Argand diagram, with unity being the smallest.
22#[derive(Builder, Clone, PartialOrd, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub struct UnityRoot {
24    /// The fraction $`k/n \in [0, 1)`$ of the unity root, represented exactly
25    /// for hashing and comparison purposes.
26    #[builder(setter(custom))]
27    pub(crate) fraction: F,
28}
29
30impl UnityRootBuilder {
31    fn fraction(&mut self, frac: F) -> &mut Self {
32        self.fraction = if F::from(0) <= frac && frac < F::from(1) {
33            Some(frac)
34        } else {
35            let numer = frac
36                .numer()
37                .unwrap_or_else(|| panic!("The numerator of {frac} cannot be extracted."));
38            let denom = frac
39                .denom()
40                .unwrap_or_else(|| panic!("The denominator of {frac} cannot be extracted."));
41            Some(F::new(numer.rem_euclid(*denom), *denom))
42        };
43        self
44    }
45}
46
47impl UnityRoot {
48    /// Returns a builder to construct a new unity root.
49    ///
50    /// # Returns
51    ///
52    /// A builder to construct a new unity root.
53    fn builder() -> UnityRootBuilder {
54        UnityRootBuilder::default()
55    }
56
57    /// Constructs a unity root $`z`$ from a non-negative index $`k`$ and order $`n`$, where
58    ///
59    /// ```math
60    ///   z = e^{\frac{2k\pi i}{n}}.
61    /// ```
62    ///
63    /// # Returns
64    ///
65    /// The required unity root.
66    #[must_use]
67    pub fn new(index: u32, order: u32) -> Self {
68        Self::builder()
69            .fraction(F::new(index, order))
70            .build()
71            .expect("Unable to construct a unity root.")
72    }
73
74    /// The order $`n`$ of the root $`z`$, *i.e.* $`z^n = 1`$.
75    ///
76    /// # Returns
77    ///
78    /// The order $`n`$.
79    fn order(&self) -> &u32 {
80        self.fraction
81            .denom()
82            .expect("Unable to obtain the order of the root.")
83    }
84
85    /// The index $`k`$ of the root $`z`$, *i.e.* $`z = e^{\frac{2k\pi i}{n}}`$
86    /// where $`k \in \mathbb{Z}/n\mathbb{Z}`$.
87    ///
88    /// # Returns
89    ///
90    /// The index $`k`$.
91    fn index(&self) -> &u32 {
92        self.fraction
93            .numer()
94            .expect("Unable to obtain the index of the root.")
95    }
96
97    /// The complex representation of this root.
98    ///
99    /// # Returns
100    ///
101    /// The complex value corresponding to this root.
102    #[must_use]
103    pub fn complex_value(&self) -> Complex<f64> {
104        let theta = self
105            .fraction
106            .to_f64()
107            .expect("Unable to convert a fraction to `f64`.")
108            * std::f64::consts::PI
109            * 2.0;
110        Complex::<f64>::from_polar(1.0, theta)
111    }
112
113    /// The complex conjugate of this root.
114    ///
115    /// # Returns
116    ///
117    /// The complex conjugate of this root.
118    ///
119    /// # Panics
120    ///
121    /// Panics when the complex conjugate cannot be found.
122    #[must_use]
123    pub fn complex_conjugate(&self) -> Self {
124        Self::new(
125            self.order().checked_sub(*self.index()).unwrap_or_else(|| {
126                panic!(
127                    "Unable to perform the subtraction `{} - {}` correctly.",
128                    self.order(),
129                    self.index()
130                )
131            }),
132            *self.order(),
133        )
134    }
135}
136
137impl<'a, 'b> Mul<&'a UnityRoot> for &'b UnityRoot {
138    type Output = UnityRoot;
139
140    fn mul(self, rhs: &'a UnityRoot) -> Self::Output {
141        #[allow(clippy::suspicious_arithmetic_impl)]
142        let fract_sum = self.fraction + rhs.fraction;
143        Self::Output::builder()
144            .fraction(fract_sum)
145            .build()
146            .unwrap_or_else(|_| {
147                panic!("Unable to construct a unity root with fraction {fract_sum}.")
148            })
149    }
150}
151
152impl Pow<i32> for &UnityRoot {
153    type Output = UnityRoot;
154
155    fn pow(self, rhs: i32) -> Self::Output {
156        Self::Output::new(
157            u32::try_from(
158                (i32::try_from(*self.index())
159                    .unwrap_or_else(|_| panic!("Unable to convert `{}` to `i32`.", self.index()))
160                    * rhs)
161                    .rem_euclid(i32::try_from(*self.order()).unwrap_or_else(|_| {
162                        panic!("Unable to convert `{}` to `i32`.", self.order())
163                    })),
164            )
165            .expect("Unexpected negative remainder."),
166            *self.order(),
167        )
168    }
169}
170
171impl fmt::Display for UnityRoot {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        if self.fraction == F::new(0u32, 4u32) {
174            write!(f, "1")
175        } else if self.fraction == F::new(1u32, 4u32) {
176            write!(f, "i")
177        } else if self.fraction == F::new(2u32, 4u32) {
178            write!(f, "-1")
179        } else if self.fraction == F::new(3u32, 4u32) {
180            write!(f, "-i")
181        } else if *self.index() == 1u32 {
182            write!(f, "E{}", self.order())
183        } else {
184            write!(f, "(E{})^{}", self.order(), self.index())
185        }
186    }
187}
188
189impl fmt::Debug for UnityRoot {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        if self.fraction == F::new(0u32, 4u32) {
192            write!(f, "1")
193        } else if *self.index() == 1u32 {
194            write!(f, "E{}", self.order())
195        } else {
196            write!(f, "(E{})^{}", self.order(), self.index())
197        }
198    }
199}