qsym2/chartab/
chartab_symbols.rs

1//! Symbols enumerating rows and columns of character tables.
2
3use std::cmp::Ordering;
4use std::collections::{HashMap, VecDeque};
5use std::error::Error;
6use std::fmt;
7use std::hash::{Hash, Hasher};
8use std::str::FromStr;
9
10use counter::Counter;
11use derive_builder::Builder;
12use indexmap::IndexMap;
13use itertools::Itertools;
14use phf::phf_map;
15use regex::Regex;
16use serde::{Deserialize, Serialize};
17
18/// Symbols for Frobenius--Schur classifications of irreducible representations.
19pub static FROBENIUS_SCHUR_SYMBOLS: phf::Map<i8, &'static str> = phf_map! {
20    1i8 => "r",
21    0i8 => "c",
22    -1i8 => "q",
23};
24
25// =================
26// Trait definitions
27// =================
28
29// ------------------
30// MathematicalSymbol
31// ------------------
32
33/// Trait for general mathematical symbols. See [`GenericSymbol`] for the definitions of the
34/// parts.
35pub trait MathematicalSymbol: Clone + Hash + Eq + fmt::Display {
36    /// The main part of the symbol.
37    fn main(&self) -> String;
38
39    /// The pre-superscript part of the symbol.
40    fn presuper(&self) -> String;
41
42    /// The pre-subscript part of the symbol.
43    fn presub(&self) -> String;
44
45    /// The post-superscript part of the symbol.
46    fn postsuper(&self) -> String;
47
48    /// The post-subscript part of the symbol.
49    fn postsub(&self) -> String;
50
51    /// The prefactor part of the symbol.
52    fn prefactor(&self) -> String;
53
54    /// The postfactor part of the symbol.
55    fn postfactor(&self) -> String;
56
57    /// The multiplicity of the symbol which can have different meanings depending on the exact
58    /// nature of the mathematical symbol.
59    fn multiplicity(&self) -> Option<usize>;
60}
61
62// -----------------
63// LinearSpaceSymbol
64// -----------------
65
66/// Trait for symbols describing linear spaces.
67pub trait LinearSpaceSymbol: MathematicalSymbol + FromStr {
68    /// The dimensionality of the linear space.
69    fn dimensionality(&self) -> usize;
70
71    /// Sets the dimensionality of the linear space for the symbol.
72    ///
73    /// # Arguments
74    ///
75    /// * `dim` - The dimensionality to be set.
76    ///
77    /// # Returns
78    ///
79    /// Returns `true` if the dimensionality has been successfully set.
80    fn set_dimensionality(&mut self, dim: usize) -> bool;
81}
82
83// --------------------------
84// ReducibleLinearSpaceSymbol
85// --------------------------
86
87// ~~~~~~~~~~~~~~~~
88// Trait definition
89// ~~~~~~~~~~~~~~~~
90
91/// Trait for symbols describing reducible linear spaces.
92pub trait ReducibleLinearSpaceSymbol: LinearSpaceSymbol
93where
94    Self::Subspace: LinearSpaceSymbol + PartialOrd,
95{
96    /// The type of the subspace symbols.
97    type Subspace;
98
99    /// Constructs [`Self`] from constituting subspace symbols and their multiplicities.
100    fn from_subspaces(subspaces: &[(Self::Subspace, usize)]) -> Self;
101
102    /// Returns the constituting subspace symbols and their multiplicities.
103    fn subspaces(&self) -> Vec<(&Self::Subspace, &usize)>;
104
105    // ----------------
106    // Provided methods
107    // ----------------
108
109    /// Returns an iterator containing sorted references to the constituting symbols.
110    ///
111    /// # Panics
112    ///
113    /// Panics if the constituting symbols cannot be ordered.
114    #[must_use]
115    fn sorted_subspaces(&self) -> Vec<(&Self::Subspace, &usize)> {
116        self.subspaces()
117            .iter()
118            .sorted_by(|(a, _), (b, _)| {
119                a.partial_cmp(b)
120                    .unwrap_or_else(|| panic!("`{a}` and `{b}` cannot be compared."))
121            })
122            .cloned()
123            .collect::<Vec<_>>()
124    }
125}
126
127// ~~~~~~~~~~~~~~~~~~~~~~
128// Blanket implementation
129// ~~~~~~~~~~~~~~~~~~~~~~
130
131impl<R> MathematicalSymbol for R
132where
133    R: ReducibleLinearSpaceSymbol,
134{
135    /// The main part of the symbol.
136    fn main(&self) -> String {
137        self.subspaces()
138            .iter()
139            .map(|(irrep, &mult)| {
140                format!(
141                    "{}{irrep}",
142                    if mult != 1 {
143                        mult.to_string()
144                    } else {
145                        String::new()
146                    }
147                )
148            })
149            .join(" ⊕ ")
150    }
151
152    /// The pre-superscript part of the symbol, which is always empty.
153    fn presuper(&self) -> String {
154        String::new()
155    }
156
157    fn presub(&self) -> String {
158        String::new()
159    }
160
161    /// The post-superscript part of the symbol, which is always empty.
162    fn postsuper(&self) -> String {
163        String::new()
164    }
165
166    /// The post-subscript part of the symbol, which is always empty.
167    fn postsub(&self) -> String {
168        String::new()
169    }
170
171    /// The prefactor part of the symbol, which is always `"1"`.
172    fn prefactor(&self) -> String {
173        "1".to_string()
174    }
175
176    /// The postfactor part of the symbol, which is always empty.
177    fn postfactor(&self) -> String {
178        String::new()
179    }
180
181    /// The multiplicity of the symbol, which is always `"1"`.
182    fn multiplicity(&self) -> Option<usize> {
183        Some(1)
184    }
185}
186
187impl<R> LinearSpaceSymbol for R
188where
189    R: ReducibleLinearSpaceSymbol,
190{
191    fn dimensionality(&self) -> usize {
192        self.subspaces()
193            .iter()
194            .map(|(symbol, &mult)| symbol.dimensionality() * mult)
195            .sum()
196    }
197
198    fn set_dimensionality(&mut self, _: usize) -> bool {
199        log::error!("The dimensionality of `{self}` cannot be set.");
200        false
201    }
202}
203
204// ----------------
205// CollectionSymbol
206// ----------------
207
208/// Trait for symbols describing collections of objects such as conjugacy classes.
209pub trait CollectionSymbol: MathematicalSymbol {
210    /// The type of the elements in the collection.
211    type CollectionElement;
212
213    /// Constructs a collection symbol from a string and one or more representative collection
214    /// elements.
215    ///
216    /// # Arguments
217    ///
218    /// * `symstr` - A string to be parsed. See [`GenericSymbol::from_str`] for more information.
219    /// * `reps` - An optional vector of one or more representative collection elements.
220    ///
221    /// # Errors
222    ///
223    /// Returns an error if `symstr` cannot be parsed.
224    fn from_reps(
225        symstr: &str,
226        reps: Option<Vec<Self::CollectionElement>>,
227    ) -> Result<Self, GenericSymbolParsingError>;
228
229    /// The first representative element of the collection.
230    fn representative(&self) -> Option<&Self::CollectionElement>;
231
232    /// All representative elements of the collection.
233    fn representatives(&self) -> Option<&Vec<Self::CollectionElement>>;
234
235    /// The size of the collection which is given by the number of representative elements
236    /// multiplied by the multiplicity of the symbol. If no representative elements exist, then the
237    /// size is taken to be the multiplicity of the symbol itself.
238    fn size(&self) -> usize {
239        self.multiplicity().unwrap_or_else(|| {
240            panic!(
241                "Unable to deduce the multiplicity of the class from the prefactor {}.",
242                self.prefactor()
243            )
244        }) * self
245            .representatives()
246            .map(|reps| reps.len())
247            .expect("No representatives found.")
248    }
249}
250
251// =======
252// Structs
253// =======
254
255// -------------
256// GenericSymbol
257// -------------
258
259// ~~~~~~~~~~~~~~~~~
260// Struct definition
261// ~~~~~~~~~~~~~~~~~
262
263/// Structure to handle generic mathematical symbols.
264///
265/// Each generic symbol has the format
266///
267/// ```math
268/// \textrm{prefactor}
269/// \ ^{\textrm{presuper}}_{\textrm{presub}}
270/// \ \textrm{main}
271/// \ ^{\textrm{postsuper}}_{\textrm{postsub}}
272/// \ \textrm{postfactor}.
273/// ```
274#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Serialize, Deserialize)]
275pub struct GenericSymbol {
276    /// The main part of the symbol.
277    main: String,
278
279    /// The pre-superscript part of the symbol.
280    #[builder(default = "String::new()")]
281    presuper: String,
282
283    /// The pre-subscript part of the symbol.
284    #[builder(default = "String::new()")]
285    presub: String,
286
287    /// The post-superscript part of the symbol.
288    #[builder(default = "String::new()")]
289    postsuper: String,
290
291    /// The post-subscript part of the symbol.
292    #[builder(default = "String::new()")]
293    postsub: String,
294
295    /// The prefactor part of the symbol.
296    #[builder(default = "String::new()")]
297    prefactor: String,
298
299    /// The postfactor part of the symbol.
300    #[builder(default = "String::new()")]
301    postfactor: String,
302}
303
304impl GenericSymbol {
305    fn builder() -> GenericSymbolBuilder {
306        GenericSymbolBuilder::default()
307    }
308
309    /// Sets the main part of the symbol.
310    pub(crate) fn set_main(&mut self, main: &str) {
311        self.main = main.to_string();
312    }
313
314    /// Sets the pre-subscript part of the symbol.
315    pub(crate) fn set_presub(&mut self, presub: &str) {
316        self.presub = presub.to_string();
317    }
318
319    /// Sets the post-subscript part of the symbol.
320    pub(crate) fn set_postsub(&mut self, postsub: &str) {
321        self.postsub = postsub.to_string();
322    }
323}
324
325// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
326// Trait implementation for GenericSymbol
327// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
328
329impl MathematicalSymbol for GenericSymbol {
330    fn main(&self) -> String {
331        self.main.clone()
332    }
333
334    fn presuper(&self) -> String {
335        self.presuper.clone()
336    }
337
338    fn presub(&self) -> String {
339        self.presub.clone()
340    }
341
342    fn postsuper(&self) -> String {
343        self.postsuper.clone()
344    }
345
346    fn postsub(&self) -> String {
347        self.postsub.clone()
348    }
349
350    fn prefactor(&self) -> String {
351        self.prefactor.clone()
352    }
353
354    fn postfactor(&self) -> String {
355        self.postfactor.clone()
356    }
357
358    fn multiplicity(&self) -> Option<usize> {
359        str::parse::<usize>(&self.prefactor).ok()
360    }
361}
362
363impl FromStr for GenericSymbol {
364    type Err = GenericSymbolParsingError;
365
366    /// Parses a string representing a generic symbol.
367    ///
368    /// Some permissible generic symbols:
369    ///
370    /// ```text
371    /// "T"
372    /// "||T|_(2g)|"
373    /// "|^(3)|T|_(2g)|"
374    /// "12||C|^(2)_(5)|"
375    /// "2||S|^(z)|(α)"
376    /// ```
377    fn from_str(symstr: &str) -> Result<Self, Self::Err> {
378        let strs: Vec<&str> = symstr.split('|').collect();
379        if strs.len() == 1 {
380            Ok(Self::builder()
381                .main(strs[0].to_string())
382                .build()
383                .unwrap_or_else(|_| {
384                    panic!("Unable to construct a generic symbol from `{symstr}`.")
385                }))
386        } else if strs.len() == 5 {
387            let prefacstr = strs[0];
388            let prestr = strs[1];
389            let mainstr = strs[2];
390            let poststr = strs[3];
391            let postfacstr = strs[4];
392
393            let presuper_re = Regex::new(r"\^\((.*?)\)").expect("Regex pattern invalid.");
394            let presuperstr = if let Some(cap) = presuper_re.captures(prestr) {
395                cap.get(1)
396                    .expect("Expected regex group cannot be captured.")
397                    .as_str()
398            } else {
399                ""
400            };
401
402            let presub_re = Regex::new(r"_\((.*?)\)").expect("Regex pattern invalid.");
403            let presubstr = if let Some(cap) = presub_re.captures(prestr) {
404                cap.get(1)
405                    .expect("Expected regex group cannot be captured.")
406                    .as_str()
407            } else {
408                ""
409            };
410
411            let postsuper_re = Regex::new(r"\^\((.*?)\)").expect("Regex pattern invalid.");
412            let postsuperstr = if let Some(cap) = postsuper_re.captures(poststr) {
413                cap.get(1)
414                    .expect("Expected regex group cannot be captured.")
415                    .as_str()
416            } else {
417                ""
418            };
419
420            let postsub_re = Regex::new(r"_\((.*?)\)").expect("Regex pattern invalid.");
421            let postsubstr = if let Some(cap) = postsub_re.captures(poststr) {
422                cap.get(1)
423                    .expect("Expected regex group cannot be captured.")
424                    .as_str()
425            } else {
426                ""
427            };
428
429            Ok(Self::builder()
430                .main(mainstr.to_string())
431                .presuper(presuperstr.to_string())
432                .presub(presubstr.to_string())
433                .postsuper(postsuperstr.to_string())
434                .postsub(postsubstr.to_string())
435                .prefactor(prefacstr.to_string())
436                .postfactor(postfacstr.to_string())
437                .build()
438                .unwrap_or_else(|_| {
439                    panic!("Unable to construct a generic symbol from `{symstr}`.")
440                }))
441        } else {
442            Err(GenericSymbolParsingError(format!(
443                "`{symstr}` is not parsable."
444            )))
445        }
446    }
447}
448
449impl fmt::Display for GenericSymbol {
450    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451        let prefac_str = if self.prefactor() == "1" {
452            String::new()
453        } else {
454            self.prefactor()
455        };
456        let presuper_str = if self.presuper().is_empty() {
457            String::new()
458        } else {
459            format!("^({})", self.presuper())
460        };
461        let presub_str = if self.presub().is_empty() {
462            String::new()
463        } else {
464            format!("_({})", self.presub())
465        };
466        let main_str = format!("|{}|", self.main());
467        let postsuper_str = if self.postsuper().is_empty() {
468            String::new()
469        } else {
470            format!("^({})", self.postsuper())
471        };
472        let postsub_str = if self.postsub().is_empty() {
473            String::new()
474        } else {
475            format!("_({})", self.postsub())
476        };
477        let postfac_str = if self.postfactor() == "1" {
478            String::new()
479        } else {
480            self.postfactor()
481        };
482        write!(
483            f,
484            "{prefac_str}{presuper_str}{presub_str}{main_str}{postsuper_str}{postsub_str}{postfac_str}",
485        )
486    }
487}
488
489#[derive(Debug, Clone)]
490pub struct GenericSymbolParsingError(pub String);
491
492impl fmt::Display for GenericSymbolParsingError {
493    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
494        write!(f, "Generic symbol parsing error: {}.", self.0)
495    }
496}
497
498impl Error for GenericSymbolParsingError {}
499
500// ----------------
501// DecomposedSymbol
502// ----------------
503
504// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
505// Trait implementation for DecomposedSymbol
506// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
507
508/// Structure to handle symbols consisting of multiple sub-symbols.
509#[derive(Builder, Debug, Clone, Eq, Serialize, Deserialize)]
510pub struct DecomposedSymbol<S>
511where
512    S: LinearSpaceSymbol + PartialOrd,
513{
514    symbols: IndexMap<S, usize>,
515}
516
517impl<S> DecomposedSymbol<S>
518where
519    S: LinearSpaceSymbol + PartialOrd,
520{
521    fn builder() -> DecomposedSymbolBuilder<S> {
522        DecomposedSymbolBuilder::<S>::default()
523    }
524
525    /// Parses a string representing a decomposed symbol. See [`Self::from_str`] for more
526    /// information.
527    ///
528    /// # Arguments
529    ///
530    /// * `symstr` - A string to be parsed to give a decomposed symbol.
531    ///
532    /// # Errors
533    ///
534    /// Errors when the string cannot be parsed as a decomposed symbol.
535    pub fn new(symstr: &str) -> Result<Self, DecomposedSymbolBuilderError> {
536        Self::from_str(symstr)
537    }
538}
539
540// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
541// Trait implementation for DecomposedSymbol
542// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
543
544impl<S> ReducibleLinearSpaceSymbol for DecomposedSymbol<S>
545where
546    S: LinearSpaceSymbol + PartialOrd,
547{
548    type Subspace = S;
549
550    fn from_subspaces(irreps: &[(Self::Subspace, usize)]) -> Self {
551        Self::builder()
552            .symbols(
553                irreps
554                    .iter()
555                    .filter(|(_, mult)| *mult != 0)
556                    .cloned()
557                    .collect::<IndexMap<_, _>>(),
558            )
559            .build()
560            .expect("Unable to construct a decomposed symbol from a slice of symbols.")
561    }
562
563    fn subspaces(&self) -> Vec<(&Self::Subspace, &usize)> {
564        self.symbols.iter().collect::<Vec<_>>()
565    }
566}
567
568impl<S> FromStr for DecomposedSymbol<S>
569where
570    S: LinearSpaceSymbol + PartialOrd + FromStr,
571{
572    type Err = DecomposedSymbolBuilderError;
573
574    /// Parses a string representing a decomposed symbol. A valid string representing a
575    /// decomposed symbol is one consisting of one or more valid symbol strings, separated by a `+`
576    /// character.
577    ///
578    /// # Arguments
579    ///
580    /// * `symstr` - A string to be parsed to give a decomposed symbol.
581    ///
582    /// # Returns
583    ///
584    /// A [`Result`] wrapping the constructed decomposed symbol.
585    ///
586    /// # Panics
587    ///
588    /// Panics when unable to construct a decomposed symbol from the specified string.
589    ///
590    /// # Errors
591    ///
592    /// Errors when the string cannot be parsed.
593    fn from_str(symstr: &str) -> Result<Self, Self::Err> {
594        let re = Regex::new(r"(\d?)(.*)").expect("Regex pattern invalid.");
595        let symbols = symstr
596            .split('⊕')
597            .map(|irrep_str| {
598                let cap = re
599                    .captures(irrep_str.trim())
600                    .unwrap_or_else(|| panic!("{irrep_str} does not fit the expected pattern."));
601                let mult_str = cap
602                    .get(1)
603                    .expect("Unable to parse the multiplicity of the irrep.")
604                    .as_str();
605                let mult = if mult_str.is_empty() {
606                    1
607                } else {
608                    str::parse::<usize>(mult_str)
609                        .unwrap_or_else(|_| panic!("`{mult_str}` is not a positive integer."))
610                };
611                let irrep = cap.get(2).expect("Unable to parse the irrep.").as_str();
612                (
613                    S::from_str(irrep)
614                        .unwrap_or_else(|_| panic!("Unable to parse {irrep} as a valid symbol.")),
615                    mult,
616                )
617            })
618            .collect::<IndexMap<_, _>>();
619        Self::builder().symbols(symbols).build()
620    }
621}
622
623impl<S> Hash for DecomposedSymbol<S>
624where
625    S: LinearSpaceSymbol + PartialOrd,
626{
627    fn hash<H: Hasher>(&self, state: &mut H) {
628        for symbol in self.sorted_subspaces() {
629            symbol.hash(state);
630        }
631    }
632}
633
634impl<S> PartialOrd for DecomposedSymbol<S>
635where
636    S: LinearSpaceSymbol + PartialOrd,
637{
638    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
639        let self_subspaces = self.sorted_subspaces();
640        let other_subspaces = other.sorted_subspaces();
641        self_subspaces.partial_cmp(&other_subspaces)
642    }
643}
644
645impl<S> fmt::Display for DecomposedSymbol<S>
646where
647    S: LinearSpaceSymbol + PartialOrd,
648{
649    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
650        write!(f, "{}", self.main())
651    }
652}
653
654impl<S> PartialEq for DecomposedSymbol<S>
655where
656    S: LinearSpaceSymbol + PartialOrd,
657{
658    fn eq(&self, other: &Self) -> bool {
659        let self_subspaces = self.sorted_subspaces();
660        let other_subspaces = other.sorted_subspaces();
661        self_subspaces == other_subspaces
662    }
663}
664
665// ================
666// Helper functions
667// ================
668
669/// Disambiguates linear-space labelling symbols that cannot be otherwise distinguished from rules.
670///
671/// This essentially appends appropriate roman subscripts to otherwise identical symbols.
672///
673/// # Arguments
674///
675/// * `raw_symbols` - An iterator of raw symbols, some of which might be identical.
676///
677/// # Returns
678///
679/// A vector of disambiguated symbols.
680pub(crate) fn disambiguate_linspace_symbols<S>(
681    raw_symbols: impl Iterator<Item = S> + Clone,
682) -> Vec<S>
683where
684    S: LinearSpaceSymbol,
685{
686    let raw_symbol_count = raw_symbols.clone().collect::<Counter<S>>();
687    let mut raw_symbols_to_full_symbols: HashMap<S, VecDeque<S>> = raw_symbol_count
688        .iter()
689        .map(|(raw_symbol, &duplicate_count)| {
690            if duplicate_count == 1 {
691                let mut symbols: VecDeque<S> = VecDeque::new();
692                symbols.push_back(raw_symbol.clone());
693                (raw_symbol.clone(), symbols)
694            } else {
695                let symbols: VecDeque<S> = (0..duplicate_count)
696                    .map(|i| {
697                        let mut new_symbol = S::from_str(&format!(
698                            "|^({})_({})|{}|^({})_({}{})|",
699                            raw_symbol.presuper(),
700                            raw_symbol.presub(),
701                            raw_symbol.main(),
702                            raw_symbol.postsuper(),
703                            i + 1,
704                            raw_symbol.postsub(),
705                        ))
706                        .unwrap_or_else(|_| {
707                            panic!(
708                                "Unable to construct symmetry symbol `|^({})|{}|^({})_({}{})|`.",
709                                raw_symbol.presuper(),
710                                raw_symbol.main(),
711                                raw_symbol.postsuper(),
712                                i + 1,
713                                raw_symbol.postsub(),
714                            )
715                        });
716                        new_symbol.set_dimensionality(raw_symbol.dimensionality());
717                        new_symbol
718                    })
719                    .collect();
720                (raw_symbol.clone(), symbols)
721            }
722        })
723        .collect();
724
725    let symbols: Vec<S> = raw_symbols
726        .map(|raw_symbol| {
727            raw_symbols_to_full_symbols
728                .get_mut(&raw_symbol)
729                .unwrap_or_else(|| {
730                    panic!(
731                        "Unknown conversion of raw symbol `{}` to full symbol.",
732                        &raw_symbol
733                    )
734                })
735                .pop_front()
736                .unwrap_or_else(|| {
737                    panic!(
738                        "No conversion to full symbol possible for `{}`",
739                        &raw_symbol
740                    )
741                })
742        })
743        .collect();
744
745    symbols
746}