qsym2/chartab/
character.rs

1//! Symbolic characters as algebraic integers that are sums of unity roots.
2
3use std::cmp::Ordering;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::ops::{Add, Mul, MulAssign, Neg, Sub};
7
8use approx;
9use derive_builder::Builder;
10use indexmap::{IndexMap, IndexSet};
11use num::Complex;
12use num_traits::{ToPrimitive, Zero};
13use serde::{Deserialize, Serialize};
14
15use crate::auxiliary::misc::HashableFloat;
16use crate::chartab::unityroot::UnityRoot;
17
18type F = fraction::GenericFraction<u32>;
19
20#[cfg(test)]
21#[path = "character_tests.rs"]
22mod character_tests;
23
24// ==================
25// Struct definitions
26// ==================
27
28/// Structure to represent algebraic group characters.
29///
30/// Partial orders between characters are based on their complex moduli and
31/// phases in the interval $`[0, 2\pi)`$ with $`0`$ being the smallest.
32#[derive(Builder, Clone, Serialize, Deserialize)]
33pub struct Character {
34    /// The unity roots and their multiplicities constituting this character.
35    #[builder(setter(custom))]
36    terms: IndexMap<UnityRoot, usize>,
37
38    /// A threshold for approximate partial ordering comparisons.
39    #[builder(setter(custom), default = "1e-14")]
40    threshold: f64,
41}
42
43impl CharacterBuilder {
44    fn terms(&mut self, ts: &[(UnityRoot, usize)]) -> &mut Self {
45        let mut terms = IndexMap::<UnityRoot, usize>::new();
46        // This ensures that if there are two identical unity roots in ts, their multiplicities are
47        // accumulated.
48        for (ur, mult) in ts.iter() {
49            *terms.entry(ur.clone()).or_default() += mult;
50        }
51        self.terms = Some(terms);
52        self
53    }
54
55    fn threshold(&mut self, thresh: f64) -> &mut Self {
56        if thresh >= 0.0 {
57            self.threshold = Some(thresh);
58        } else {
59            log::error!(
60                "Threshold value {} is invalid. Threshold must be non-negative.",
61                thresh
62            );
63            self.threshold = None;
64        }
65        self
66    }
67}
68
69impl Character {
70    /// Returns a builder to construct a new character.
71    ///
72    /// # Returns
73    ///
74    /// A builder to construct a new character.
75    fn builder() -> CharacterBuilder {
76        CharacterBuilder::default()
77    }
78
79    /// Constructs a character from an array of unity roots and multiplicities.
80    ///
81    /// # Returns
82    ///
83    /// A character.
84    #[must_use]
85    pub fn new(ts: &[(UnityRoot, usize)]) -> Self {
86        Self::builder()
87            .terms(ts)
88            .build()
89            .expect("Unable to construct a character.")
90    }
91
92    /// Returns the threshold for approximate partial ordering comparisons.
93    pub fn threshold(&self) -> f64 {
94        self.threshold
95    }
96
97    /// The complex representation of this character.
98    ///
99    /// # Returns
100    ///
101    /// The complex value corresponding to this character.
102    ///
103    /// # Panics
104    ///
105    /// Panics when encountering any multiplicity that cannot be converted to `f64`.
106    #[must_use]
107    pub fn complex_value(&self) -> Complex<f64> {
108        self.terms
109            .iter()
110            .filter_map(|(uroot, &mult)| {
111                if mult > 0 {
112                    Some(
113                        uroot.complex_value()
114                            * mult
115                                .to_f64()
116                                .unwrap_or_else(|| panic!("Unable to convert `{mult}` to `f64`.")),
117                    )
118                } else {
119                    None
120                }
121            })
122            .sum()
123    }
124
125    /// Gets a numerical form for this character, nicely formatted up to a
126    /// required precision.
127    ///
128    /// # Arguments
129    ///
130    /// * `precision` - The number of decimal places.
131    ///
132    /// # Returns
133    ///
134    /// The formatted numerical form.
135    #[must_use]
136    pub fn get_numerical(&self, real_only: bool, precision: usize) -> String {
137        let Complex { re, im } = self.complex_value();
138        if real_only {
139            format!("{:+.precision$}", {
140                if approx::relative_eq!(
141                    re,
142                    0.0,
143                    epsilon = self.threshold,
144                    max_relative = self.threshold
145                ) && re < 0.0
146                {
147                    -re
148                } else {
149                    re
150                }
151            })
152        } else {
153            format!(
154                "{:+.precision$} {} {:.precision$}i",
155                {
156                    if approx::relative_eq!(
157                        re,
158                        0.0,
159                        epsilon = self.threshold,
160                        max_relative = self.threshold
161                    ) && re < 0.0
162                    {
163                        -re
164                    } else {
165                        re
166                    }
167                },
168                {
169                    if im >= 0.0
170                        || approx::relative_eq!(
171                            im,
172                            0.0,
173                            epsilon = self.threshold,
174                            max_relative = self.threshold
175                        )
176                    {
177                        "+"
178                    } else {
179                        "-"
180                    }
181                },
182                im.abs()
183            )
184        }
185    }
186
187    /// Gets the concise form for this character.
188    ///
189    /// The concise form shows an integer or an integer followed by $`i`$ if the character is
190    /// purely integer or integer imaginary. Otherwise, the concise form is either the analytic
191    /// form of the character showing all contributing unity roots and their multiplicities, or a
192    /// complex number formatted to 3 d.p.
193    ///
194    /// # Arguments
195    ///
196    /// * `num_non_int` - A flag indicating of non-integers should be shown in numerical form
197    /// instead of analytic.
198    ///
199    /// # Returns
200    ///
201    /// The concise form of the character.
202    fn get_concise(&self, num_non_int: bool) -> String {
203        let complex_value = self.complex_value();
204        let precision = 3i32;
205        let precision_u = usize::try_from(precision.unsigned_abs())
206            .expect("Unable to represent `precision` as `usize`.");
207        if approx::relative_eq!(
208            complex_value.im,
209            0.0,
210            epsilon = 10.0f64.powi(-precision - 1).max(self.threshold),
211            max_relative = 10.0f64.powi(-precision - 1).max(self.threshold)
212        ) {
213            // Zero imaginary
214            // Zero or non-zero real
215            let rounded_re = complex_value.re.round_factor(self.threshold);
216            if approx::relative_eq!(
217                rounded_re,
218                rounded_re.round(),
219                epsilon = 10.0f64.powi(-precision - 1).max(self.threshold),
220                max_relative = 10.0f64.powi(-precision - 1).max(self.threshold)
221            ) {
222                // Integer real
223                if approx::relative_eq!(
224                    rounded_re,
225                    0.0,
226                    epsilon = 10.0f64.powi(-precision - 1).max(self.threshold),
227                    max_relative = 10.0f64.powi(-precision - 1).max(self.threshold)
228                ) {
229                    "0".to_string()
230                } else {
231                    format!("{:+.0}", complex_value.re)
232                }
233            } else {
234                // Non-integer real
235                if num_non_int {
236                    format!("{:+.precision_u$}", complex_value.re)
237                } else {
238                    format!("{self:?}")
239                }
240            }
241        } else if approx::relative_eq!(
242            complex_value.re,
243            0.0,
244            epsilon = self.threshold,
245            max_relative = self.threshold
246        ) {
247            // Non-zero imaginary
248            // Zero real
249            let rounded_im = complex_value.im.round_factor(self.threshold);
250            if approx::relative_eq!(
251                rounded_im,
252                rounded_im.round(),
253                epsilon = 10.0f64.powi(-precision - 1).max(self.threshold),
254                max_relative = 10.0f64.powi(-precision - 1).max(self.threshold)
255            ) {
256                // Integer imaginary
257                if approx::relative_eq!(
258                    rounded_im.abs(),
259                    1.0,
260                    epsilon = 10.0f64.powi(-precision - 1).max(self.threshold),
261                    max_relative = 10.0f64.powi(-precision - 1).max(self.threshold)
262                ) {
263                    // i or -i
264                    let imag = if rounded_im > 0.0 { "+i" } else { "-i" };
265                    imag.to_string()
266                } else {
267                    // ki
268                    format!("{:+.0}i", complex_value.im)
269                }
270            } else {
271                // Non-integer imaginary
272                if num_non_int {
273                    format!("{:+.precision_u$}i", complex_value.im)
274                } else {
275                    format!("{self:?}")
276                }
277            }
278        } else {
279            // Non-zero imaginary
280            // Non-zero real
281            let rounded_re = complex_value.re.round_factor(self.threshold);
282            let rounded_im = complex_value.im.round_factor(self.threshold);
283            if (approx::relative_ne!(
284                rounded_re,
285                rounded_re.round(),
286                epsilon = 10.0f64.powi(-precision - 1).max(self.threshold),
287                max_relative = 10.0f64.powi(-precision - 1).max(self.threshold)
288            ) || approx::relative_ne!(
289                rounded_im,
290                rounded_im.round(),
291                epsilon = 10.0f64.powi(-precision - 1).max(self.threshold),
292                max_relative = 10.0f64.powi(-precision - 1).max(self.threshold)
293            )) && !num_non_int
294            {
295                format!("{self:?}")
296            } else {
297                format!(
298                    "{:+.precision_u$} {} {:.precision_u$}i",
299                    complex_value.re,
300                    {
301                        if complex_value.im > 0.0 {
302                            "+"
303                        } else {
304                            "-"
305                        }
306                    },
307                    complex_value.im.abs()
308                )
309            }
310        }
311    }
312
313    /// Gets the simplified form for this character.
314    ///
315    /// The simplified form gathers terms whose unity roots differ from each other by a factor of
316    /// $`-1`$.
317    ///
318    /// # Returns
319    ///
320    /// The simplified form of the character.
321    ///
322    /// # Panics
323    ///
324    /// Panics when the multiplicity of any unitary root cannot be retrieved.
325    #[must_use]
326    pub fn simplify(&self) -> Self {
327        let mut urs: IndexSet<_> = self.terms.keys().rev().collect();
328        let mut simplified_terms = Vec::<(UnityRoot, usize)>::with_capacity(urs.len());
329        let f12 = F::new(1u32, 2u32);
330        while !urs.is_empty() {
331            let ur = urs
332                .pop()
333                .expect("Unable to retrieve an unexamined unity root.");
334            let nur_option = urs
335                .iter()
336                .find(|&test_ur| {
337                    test_ur.fraction == ur.fraction + f12 || test_ur.fraction == ur.fraction - f12
338                })
339                .copied();
340            if let Some(nur) = nur_option {
341                let res = urs.shift_remove(nur);
342                debug_assert!(res);
343                let ur_mult = self
344                    .terms
345                    .get(ur)
346                    .unwrap_or_else(|| panic!("Unable to retrieve the multiplicity of {ur}."));
347                let nur_mult = self
348                    .terms
349                    .get(nur)
350                    .unwrap_or_else(|| panic!("Unable to retrieve the multiplicity of {nur}."));
351                match ur_mult.cmp(nur_mult) {
352                    Ordering::Less => simplified_terms.push((nur.clone(), nur_mult - ur_mult)),
353                    Ordering::Greater => simplified_terms.push((ur.clone(), ur_mult - nur_mult)),
354                    Ordering::Equal => (),
355                };
356            } else {
357                let ur_mult = self
358                    .terms
359                    .get(ur)
360                    .unwrap_or_else(|| panic!("Unable to retrieve the multiplicity of {ur}."));
361                simplified_terms.push((ur.clone(), *ur_mult));
362            }
363        }
364        Character::builder()
365            .terms(&simplified_terms)
366            .threshold(self.threshold)
367            .build()
368            .expect("Unable to construct a simplified character.")
369    }
370
371    /// The complex conjugate of this character.
372    ///
373    /// # Returns
374    ///
375    /// The complex conjugate of this character.
376    ///
377    /// # Panics
378    ///
379    /// Panics when the complex conjugate cannot be found.
380    #[must_use]
381    pub fn complex_conjugate(&self) -> Self {
382        Self::builder()
383            .terms(
384                &self
385                    .terms
386                    .iter()
387                    .map(|(ur, mult)| (ur.complex_conjugate(), *mult))
388                    .collect::<Vec<_>>(),
389            )
390            .threshold(self.threshold)
391            .build()
392            .unwrap_or_else(|_| panic!("Unable to construct the complex conjugate of `{self}`."))
393    }
394}
395
396// =====================
397// Trait implementations
398// =====================
399
400impl PartialEq for Character {
401    fn eq(&self, other: &Self) -> bool {
402        (self.terms == other.terms) || {
403            let self_complex = self.complex_value();
404            let other_complex = other.complex_value();
405            let thresh = (self.threshold * other.threshold).sqrt();
406            approx::relative_eq!(
407                self_complex.re,
408                other_complex.re,
409                epsilon = thresh,
410                max_relative = thresh
411            ) && approx::relative_eq!(
412                self_complex.im,
413                other_complex.im,
414                epsilon = thresh,
415                max_relative = thresh
416            )
417        }
418    }
419}
420
421impl Eq for Character {}
422
423impl PartialOrd for Character {
424    /// Two characters are compared based on their constituent unity roots.
425    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
426        let mut self_terms = self.terms.clone();
427        self_terms.retain(|_, mult| *mult > 0);
428        self_terms.sort_by(|uroot1, _, uroot2, _| {
429            uroot1
430                .partial_cmp(uroot2)
431                .unwrap_or_else(|| panic!("{uroot1} and {uroot2} cannot be compared."))
432        });
433
434        let mut other_terms = other.terms.clone();
435        other_terms.retain(|_, mult| *mult > 0);
436        other_terms.sort_by(|uroot1, _, uroot2, _| {
437            uroot1
438                .partial_cmp(uroot2)
439                .unwrap_or_else(|| panic!("{uroot1} and {uroot2} cannot be compared."))
440        });
441
442        let self_terms_vec = self_terms.into_iter().collect::<Vec<_>>();
443        let other_terms_vec = other_terms.into_iter().collect::<Vec<_>>();
444        self_terms_vec.partial_cmp(&other_terms_vec)
445    }
446}
447
448impl fmt::Debug for Character {
449    /// Prints the full form for this character showing all contributing unity
450    /// roots and their multiplicities.
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        let one = UnityRoot::new(0u32, 2u32);
453        let str_terms: Vec<String> = self
454            .terms
455            .clone()
456            .sorted_by(|k1, _, k2, _| {
457                k1.partial_cmp(k2)
458                    .unwrap_or_else(|| panic!("{k1} and {k2} cannot be compared."))
459            })
460            .filter_map(|(root, mult)| {
461                if mult == 1 {
462                    Some(format!("{root}"))
463                } else if mult == 0 {
464                    None
465                } else if root == one {
466                    Some(format!("{mult}"))
467                } else {
468                    Some(format!("{mult}*{root}"))
469                }
470            })
471            .collect();
472        if str_terms.is_empty() {
473            write!(f, "0")
474        } else {
475            write!(f, "{}", str_terms.join(" + "))
476        }
477    }
478}
479
480impl fmt::Display for Character {
481    /// Prints the short form for this character that shows either an integer
482    /// or an imaginary integer or a compact complex number at 3 d.p.
483    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484        write!(f, "{}", self.get_concise(false))
485    }
486}
487
488impl Hash for Character {
489    fn hash<H: Hasher>(&self, state: &mut H) {
490        let terms_vec = self
491            .terms
492            .clone()
493            .sorted_by(|ur1, m1, ur2, m2| {
494                PartialOrd::partial_cmp(&(ur1.clone(), m1), &(ur2.clone(), m2)).unwrap_or_else(
495                    || {
496                        panic!(
497                            "{:?} anmd {:?} cannot be compared.",
498                            (ur1.clone(), m1),
499                            (ur2.clone(), m2)
500                        )
501                    },
502                )
503            })
504            .collect::<Vec<_>>();
505        terms_vec.hash(state);
506    }
507}
508
509// ----
510// Zero
511// ----
512impl Zero for Character {
513    fn zero() -> Self {
514        Self::new(&[])
515    }
516
517    fn is_zero(&self) -> bool {
518        *self == Self::zero()
519    }
520}
521
522// ---
523// Add
524// ---
525impl Add<&'_ Character> for &Character {
526    type Output = Character;
527
528    fn add(self, rhs: &Character) -> Self::Output {
529        let mut sum = self.clone();
530        for (ur, mult) in rhs.terms.iter() {
531            *sum.terms.entry(ur.clone()).or_default() += mult;
532        }
533        sum
534    }
535}
536
537impl Add<&'_ Character> for Character {
538    type Output = Character;
539
540    fn add(self, rhs: &Self) -> Self::Output {
541        &self + rhs
542    }
543}
544
545impl Add<Character> for &Character {
546    type Output = Character;
547
548    fn add(self, rhs: Character) -> Self::Output {
549        self + &rhs
550    }
551}
552
553impl Add<Character> for Character {
554    type Output = Character;
555
556    fn add(self, rhs: Self) -> Self::Output {
557        &self + &rhs
558    }
559}
560
561// ---
562// Neg
563// ---
564impl Neg for &Character {
565    type Output = Character;
566
567    fn neg(self) -> Self::Output {
568        let f12 = F::new(1u32, 2u32);
569        let terms: IndexMap<_, _> = self
570            .terms
571            .iter()
572            .map(|(ur, mult)| {
573                let mut nur = ur.clone();
574                nur.fraction = (nur.fraction + f12).fract();
575                (nur, *mult)
576            })
577            .collect();
578        let mut nchar = self.clone();
579        nchar.terms = terms;
580        nchar
581    }
582}
583
584impl Neg for Character {
585    type Output = Character;
586
587    fn neg(self) -> Self::Output {
588        -&self
589    }
590}
591
592// ---
593// Sub
594// ---
595impl Sub<&'_ Character> for &Character {
596    type Output = Character;
597
598    fn sub(self, rhs: &Character) -> Self::Output {
599        self + (-rhs)
600    }
601}
602
603impl Sub<&'_ Character> for Character {
604    type Output = Character;
605
606    fn sub(self, rhs: &Self) -> Self::Output {
607        &self + (-rhs)
608    }
609}
610
611impl Sub<Character> for &Character {
612    type Output = Character;
613
614    fn sub(self, rhs: Character) -> Self::Output {
615        self + (-&rhs)
616    }
617}
618
619impl Sub<Character> for Character {
620    type Output = Character;
621
622    fn sub(self, rhs: Self) -> Self::Output {
623        &self + (-&rhs)
624    }
625}
626
627// ---------
628// MulAssign
629// ---------
630impl MulAssign<usize> for Character {
631    fn mul_assign(&mut self, rhs: usize) {
632        self.terms.iter_mut().for_each(|(_, mult)| {
633            *mult *= rhs;
634        });
635    }
636}
637
638// ---
639// Mul
640// ---
641impl Mul<usize> for &Character {
642    type Output = Character;
643
644    fn mul(self, rhs: usize) -> Self::Output {
645        let mut prod = self.clone();
646        prod *= rhs;
647        prod
648    }
649}
650
651impl Mul<usize> for Character {
652    type Output = Character;
653
654    fn mul(self, rhs: usize) -> Self::Output {
655        &self * rhs
656    }
657}