qsym2/permutation/
permutation_symbols.rs

1//! Symbols for permutations, conjugacy classes, and irreducible representations.
2
3use std::fmt;
4use std::hash::{Hash, Hasher};
5use std::str::FromStr;
6
7use derive_builder::Builder;
8use itertools::Itertools;
9use ndarray::{Array2, ArrayView2, Axis};
10use num_traits::ToPrimitive;
11use serde::{Deserialize, Serialize};
12
13use crate::chartab::character::Character;
14use crate::chartab::chartab_symbols::{
15    disambiguate_linspace_symbols, CollectionSymbol, GenericSymbol, GenericSymbolParsingError,
16    LinearSpaceSymbol, MathematicalSymbol,
17};
18use crate::chartab::unityroot::UnityRoot;
19use crate::permutation::{Permutation, PermutationRank};
20
21// ==================
22// Struct definitions
23// ==================
24
25// ----------------------
26// PermutationClassSymbol
27// ----------------------
28
29/// Structure to handle conjugacy class symbols.
30#[derive(Builder, Debug, Clone, Serialize, Deserialize)]
31pub struct PermutationClassSymbol<T: PermutationRank> {
32    /// The generic part of the symbol.
33    generic_symbol: GenericSymbol,
34
35    /// A representative element in the class.
36    representatives: Option<Vec<Permutation<T>>>,
37}
38
39impl<T: PermutationRank> PartialEq for PermutationClassSymbol<T> {
40    fn eq(&self, other: &Self) -> bool {
41        self.generic_symbol == other.generic_symbol
42    }
43}
44
45impl<T: PermutationRank> Eq for PermutationClassSymbol<T> {}
46
47impl<T: PermutationRank> Hash for PermutationClassSymbol<T> {
48    fn hash<H: Hasher>(&self, state: &mut H) {
49        self.generic_symbol.hash(state);
50    }
51}
52
53impl<T: PermutationRank> PermutationClassSymbol<T> {
54    fn builder() -> PermutationClassSymbolBuilder<T> {
55        PermutationClassSymbolBuilder::default()
56    }
57
58    /// Creates a class symbol from a string and a representative element.
59    ///
60    /// Some possible conjugacy class symbols:
61    ///
62    /// ```text
63    /// "1||(5)(2)(1)||"
64    /// "1||(4)(1)|^(2)|"
65    /// "12||(2)(2)(1)|^(5)|"
66    /// ```
67    ///
68    /// Note that the prefactor is required.
69    ///
70    /// # Arguments
71    ///
72    /// * `symstr` - A string to be parsed to give a class symbol.
73    /// * `rep` - An optional representative element for this class.
74    ///
75    /// # Returns
76    ///
77    /// A [`Result`] wrapping the constructed class symbol.
78    ///
79    /// # Panics
80    ///
81    /// Panics when unable to construct a class symbol from the specified string.
82    ///
83    /// # Errors
84    ///
85    /// Errors when the string contains no parsable class size prefactor, or when the string cannot
86    /// be parsed as a generic symbol.
87    pub fn new(
88        symstr: &str,
89        reps: Option<Vec<Permutation<T>>>,
90    ) -> Result<Self, GenericSymbolParsingError> {
91        let generic_symbol = GenericSymbol::from_str(symstr)?;
92        if generic_symbol.multiplicity().is_none() {
93            Err(GenericSymbolParsingError(format!(
94                "{symstr} contains no class size prefactor."
95            )))
96        } else {
97            Ok(Self::builder()
98                .generic_symbol(generic_symbol)
99                .representatives(reps)
100                .build()
101                .unwrap_or_else(|_| panic!("Unable to construct a class symbol from `{symstr}`.")))
102        }
103    }
104}
105
106impl<T: PermutationRank> MathematicalSymbol for PermutationClassSymbol<T> {
107    /// The main part of the symbol, which denotes the cycle pattern of the class.
108    fn main(&self) -> String {
109        self.generic_symbol.main()
110    }
111
112    /// The pre-superscript part of the symbol, which is empty.
113    fn presuper(&self) -> String {
114        String::new()
115    }
116
117    /// The pre-subscript part of the symbol, which is empty.
118    fn presub(&self) -> String {
119        String::new()
120    }
121
122    /// The post-superscript part of the symbol, which is empty.
123    fn postsuper(&self) -> String {
124        String::new()
125    }
126
127    /// The post-subscript part of the symbol, which is empty.
128    fn postsub(&self) -> String {
129        String::new()
130    }
131
132    /// The prefactor part of the symbol, which denotes the size of the class.
133    fn prefactor(&self) -> String {
134        self.generic_symbol.prefactor()
135    }
136
137    /// The postfactor part of the symbol, which is empty.
138    fn postfactor(&self) -> String {
139        String::new()
140    }
141
142    /// The number of times the representative elements are 'duplicated' to give the size of the
143    /// class.
144    fn multiplicity(&self) -> Option<usize> {
145        self.generic_symbol.multiplicity()
146    }
147}
148
149impl<T: PermutationRank> CollectionSymbol for PermutationClassSymbol<T> {
150    type CollectionElement = Permutation<T>;
151
152    fn from_reps(
153        symstr: &str,
154        reps: Option<Vec<Self::CollectionElement>>,
155    ) -> Result<Self, GenericSymbolParsingError> {
156        Self::new(symstr, reps)
157    }
158
159    fn representative(&self) -> Option<&Self::CollectionElement> {
160        self.representatives.as_ref().map(|reps| &reps[0])
161    }
162
163    fn representatives(&self) -> Option<&Vec<Self::CollectionElement>> {
164        self.representatives.as_ref()
165    }
166}
167
168impl<T: PermutationRank> fmt::Display for PermutationClassSymbol<T> {
169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170        write!(f, "{}", self.generic_symbol)
171    }
172}
173
174// ----------------------
175// PermutationIrrepSymbol
176// ----------------------
177
178/// Structure to handle permutation irreducible representation symbols. This will be converted to a
179/// suitable representation of Young tableaux symbols in the future.
180#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Serialize, Deserialize)]
181pub struct PermutationIrrepSymbol {
182    /// The generic part of the symbol.
183    #[builder(setter(custom))]
184    generic_symbol: GenericSymbol,
185
186    /// The dimensionality of the irreducible representation.
187    #[builder(setter(custom), default = "None")]
188    dim: Option<usize>,
189}
190
191impl PermutationIrrepSymbolBuilder {
192    fn generic_symbol(&mut self, sym: GenericSymbol) -> &mut Self {
193        assert!(
194            sym.main() == "Sym" || sym.main() == "Alt" || sym.main() == "Λ",
195            "The main part of a permutation irrep symbol can only be `Sym`, `Alt`, or `Λ`.",
196        );
197        self.generic_symbol = Some(sym);
198        self
199    }
200
201    fn dim(&mut self, dim: usize) -> &mut Self {
202        let main = self
203            .generic_symbol
204            .as_ref()
205            .expect("The generic symbol has not been set for this permutation symbol.")
206            .main();
207        if main == "Sym" || main == "Alt" {
208            assert_eq!(
209                dim, 1,
210                "A `{main}` permutation irrep must be one-dimensional."
211            );
212        }
213        self.dim = Some(Some(dim));
214        self
215    }
216}
217
218impl PermutationIrrepSymbol {
219    fn builder() -> PermutationIrrepSymbolBuilder {
220        PermutationIrrepSymbolBuilder::default()
221    }
222
223    /// Construct a permutation irrep symbol from a string and its dimensionality.
224    ///
225    /// Some permissible permutation irrep symbols:
226    ///
227    /// ```text
228    /// "||Sym||"
229    /// "||Alt||"
230    /// "||Λ|_(1)|"
231    /// ```
232    ///
233    /// # Arguments
234    ///
235    /// * `symstr` - A string to be parsed to give a permutation symbol.
236    ///
237    /// # Errors
238    ///
239    /// Errors when the string cannot be parsed as a generic symbol.
240    pub fn new(symstr: &str, dim: usize) -> Result<Self, PermutationIrrepSymbolBuilderError> {
241        let generic_symbol = GenericSymbol::from_str(symstr)
242            .unwrap_or_else(|_| panic!("Unable to parse {symstr} as a generic symbol."));
243        Self::builder()
244            .generic_symbol(generic_symbol)
245            .dim(dim)
246            .build()
247    }
248}
249
250impl MathematicalSymbol for PermutationIrrepSymbol {
251    /// The main part of the symbol, which primarily denotes the dimensionality of the irrep space.
252    fn main(&self) -> String {
253        self.generic_symbol.main()
254    }
255
256    /// The pre-superscript part of the symbol, which can be used to denote antiunitary symmetries
257    /// or spin multiplicities.
258    fn presuper(&self) -> String {
259        self.generic_symbol.presuper()
260    }
261
262    fn presub(&self) -> String {
263        self.generic_symbol.presub()
264    }
265
266    /// The post-superscript part of the symbol, which denotes reflection parity.
267    fn postsuper(&self) -> String {
268        self.generic_symbol.postsuper()
269    }
270
271    /// The post-subscript part of the symbol, which denotes inversion parity when available and
272    /// which disambiguates similar irreps.
273    fn postsub(&self) -> String {
274        self.generic_symbol.postsub()
275    }
276
277    /// The prefactor part of the symbol, which is always `"1"` implicitly because of irreducibility.
278    fn prefactor(&self) -> String {
279        String::new()
280    }
281
282    /// The postfactor part of the symbol, which is always empty.
283    fn postfactor(&self) -> String {
284        String::new()
285    }
286
287    /// The dimensionality of the irreducible representation.
288    fn multiplicity(&self) -> Option<usize> {
289        self.dim
290    }
291}
292
293impl FromStr for PermutationIrrepSymbol {
294    type Err = PermutationIrrepSymbolBuilderError;
295
296    /// Parses a string representing a permutation irrep symbol.
297    ///
298    /// Some permissible permutation irrep symbols:
299    ///
300    /// ```text
301    /// "||Sym||"
302    /// "||Alt||"
303    /// "||Λ|_(1)|"
304    /// ```
305    ///
306    /// # Arguments
307    ///
308    /// * `symstr` - A string to be parsed to give a permutation symbol.
309    ///
310    /// # Errors
311    ///
312    /// Errors when the string cannot be parsed as a generic symbol.
313    fn from_str(symstr: &str) -> Result<Self, Self::Err> {
314        let generic_symbol = GenericSymbol::from_str(symstr)
315            .unwrap_or_else(|_| panic!("Unable to parse {symstr} as a generic symbol."));
316        Self::builder().generic_symbol(generic_symbol).build()
317    }
318}
319
320impl LinearSpaceSymbol for PermutationIrrepSymbol {
321    fn dimensionality(&self) -> usize {
322        self.dim
323            .unwrap_or_else(|| panic!("Unknown dimensionality for permutation irrep `{self}`."))
324    }
325
326    fn set_dimensionality(&mut self, dim: usize) -> bool {
327        if dim == 1 {
328            if self.main() == "Sym" || self.main() == "Alt" {
329                self.dim = Some(dim);
330                true
331            } else {
332                false
333            }
334        } else if self.main() != "Sym" && self.main() != "Alt" {
335            self.dim = Some(dim);
336            true
337        } else {
338            false
339        }
340    }
341}
342
343impl fmt::Display for PermutationIrrepSymbol {
344    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
345        write!(
346            f,
347            "|{}{}|{}",
348            self.main(),
349            if self.main() == "Λ" {
350                self.dim
351                    .map(|dim| dim.to_string())
352                    .unwrap_or_else(|| "?".to_string())
353            } else {
354                String::new()
355            },
356            if self.postsub().is_empty() {
357                String::new()
358            } else {
359                format!("_({})", self.postsub())
360            }
361        )
362    }
363}
364
365// =======
366// Methods
367// =======
368
369/// Sorts permutation irreps based on their dimensionalities.
370///
371/// # Arguments
372///
373/// * `char_arr` - A view of the array of characters for which the irreps are to be sorted.
374/// * `frobenius_schur_indicators` - The associated Frobenius--Schur indicators with the irreps.
375///
376/// # Returns
377///
378/// An array of characters where the irreps have been sorted, and a vector of the associated
379/// Frobenius--Schur indicators that have also been similarly sorted.
380pub(super) fn sort_perm_irreps(
381    char_arr: &ArrayView2<Character>,
382    frobenius_schur_indicators: &[i8],
383) -> (Array2<Character>, Vec<i8>) {
384    log::debug!("Sorting permutation irreducible representations...");
385    let n_rows = char_arr.nrows();
386    let col_idxs = (0..n_rows).collect_vec();
387    let sort_arr = char_arr.select(Axis(1), &col_idxs);
388
389    let sort_row_indices: Vec<_> = (0..n_rows)
390        .sorted_by(|&i, &j| {
391            let keys_i = sort_arr.row(i).iter().cloned().collect_vec();
392            let keys_j = sort_arr.row(j).iter().cloned().collect_vec();
393            keys_i
394                .partial_cmp(&keys_j)
395                .unwrap_or_else(|| panic!("`{keys_i:?}` and `{keys_j:?}` cannot be compared."))
396        })
397        .collect();
398    let char_arr = char_arr.select(Axis(0), &sort_row_indices);
399    let old_fs = frobenius_schur_indicators.iter().collect::<Vec<_>>();
400    let sorted_fs = sort_row_indices.iter().map(|&i| *old_fs[i]).collect_vec();
401    log::debug!("Sorting permutation irreducible representations... Done.");
402    (char_arr, sorted_fs)
403}
404
405/// Deduces the permutation irrep symbols based on the characters.
406///
407/// This classifies each irrep into either `Sym` for the totally symmetric irrep, `Alt` for the
408/// alternating one-dimensional irrep, or `Λ` for all higher-dimensional irreps. No attempt is made
409/// to assign Young diagrams to the irreps.
410///
411/// # Arguments
412///
413/// * `char_arr` - An array of characters.
414///
415/// # Returns
416///
417/// A vector of permutation irrep symbols.
418pub(super) fn deduce_permutation_irrep_symbols(
419    char_arr: &ArrayView2<Character>,
420) -> Vec<PermutationIrrepSymbol> {
421    log::debug!("Generating permutation irreducible representation symbols...");
422
423    // First pass: assign irrep symbols from rules as much as possible.
424    log::debug!("First pass: assign symbols from rules");
425
426    let one = Character::new(&[(UnityRoot::new(0u32, 1u32), 1)]);
427    let raw_irrep_symbols = char_arr.rows().into_iter().map(|irrep| {
428        let dim = irrep[0].clone();
429        if dim == one {
430            if irrep.iter().all(|chr| chr.clone() == one) {
431                PermutationIrrepSymbol::new("||Sym||", 1).unwrap_or_else(|_| {
432                    panic!("Unable to construct permutation irrep symbol `||Sym||`")
433                })
434            } else {
435                PermutationIrrepSymbol::new("||Alt||", 1).unwrap_or_else(|_| {
436                    panic!("Unable to construct permutation irrep symbol `||Alt||`")
437                })
438            }
439        } else {
440            let dim_c = dim.complex_value();
441            assert!(
442                approx::relative_eq!(dim_c.im, 0.0)
443                    && approx::relative_eq!(dim_c.re.round(), dim_c.re)
444                    && dim_c.re.round() > 0.0
445            );
446            let dim_u = dim_c.re
447                .round()
448                .to_usize()
449                .expect("Unable to convert the dimensionality of an irrep to `usize`.");
450            PermutationIrrepSymbol::new("||Λ||", dim_u).unwrap_or_else(|_| {
451                    panic!("Unable to construct permutation irrep symbol `||Λ||` with dimensionality `{dim_u}`.")
452                })
453        }
454    });
455
456    // Second pass: disambiguate identical cases not distinguishable by rules
457    log::debug!("Second pass: disambiguate identical cases not distinguishable by rules");
458    let irrep_symbols = disambiguate_linspace_symbols(raw_irrep_symbols);
459
460    log::debug!("Generating permutation irreducible representation symbols... Done.");
461    irrep_symbols
462}