qsym2/basis/
ao.rs

1//! Atomic-orbital basis functions.
2
3use std::cmp::Ordering;
4use std::collections::{HashMap, HashSet};
5use std::convert::TryInto;
6use std::fmt;
7use std::hash::{Hash, Hasher};
8use std::slice::Iter;
9
10use anyhow::{self, ensure, format_err};
11use counter::Counter;
12use derive_builder::Builder;
13use itertools::{Itertools, izip};
14use serde::{Deserialize, Serialize};
15
16use crate::angmom::ANGMOM_LABELS;
17use crate::auxiliary::atom::Atom;
18use crate::auxiliary::misc::ProductRepeat;
19use crate::permutation::{PermutableCollection, Permutation, permute_inplace};
20
21#[cfg(test)]
22#[path = "ao_tests.rs"]
23mod ao_tests;
24
25// -------------------
26// Shell order structs
27// -------------------
28
29// ~~~~~~~~~
30// PureOrder
31// ~~~~~~~~~
32
33/// Structure to contain information about the ordering of pure Gaussians of a certain rank.
34#[derive(Clone, Builder, PartialEq, Eq, Hash)]
35pub struct PureOrder {
36    /// A sequence of $`m_l`$ values giving the ordering of the pure Gaussians.
37    #[builder(setter(custom))]
38    mls: Vec<i32>,
39
40    /// The rank of the pure Gaussians.
41    pub lpure: u32,
42
43    /// The spatial parity of the pure Gaussians: `true` if even under spatial inversion and
44    /// `false` if odd. By default, this is the same as the parity of `Self::lpure`.
45    #[builder(default = "self.default_even()?")]
46    pub even: bool,
47}
48
49impl PureOrderBuilder {
50    fn mls(&mut self, mls: &[i32]) -> &mut Self {
51        let lpure = self.lpure.expect("`lpure` has not been set.");
52        assert!(
53            mls.iter()
54                .map(|m| m.unsigned_abs())
55                .max()
56                .expect("The maximum |m| value could not be determined.")
57                == lpure
58        );
59        assert_eq!(mls.len(), (2 * lpure + 1) as usize);
60        self.mls = Some(mls.to_vec());
61        self
62    }
63
64    fn default_even(&self) -> Result<bool, String> {
65        let lpure = self
66            .lpure
67            .ok_or_else(|| "`lpure` has not been set.".to_string())?;
68        Ok(lpure % 2 == 0)
69    }
70}
71
72impl PureOrder {
73    /// Returns a builder to construct a new [`PureOrder`] structure.
74    fn builder() -> PureOrderBuilder {
75        PureOrderBuilder::default()
76    }
77
78    /// Constructs a new [`PureOrder`] structure from its constituting $`m_l`$ values. The spatial
79    /// inversion parity is determined by the parity of the deduced $`l`$ value.
80    pub fn new(mls: &[i32]) -> Result<Self, anyhow::Error> {
81        let lpure = mls
82            .iter()
83            .map(|m| m.unsigned_abs())
84            .max()
85            .expect("The maximum |m| value could not be determined.");
86        let pure_order = PureOrder::builder()
87            .lpure(lpure)
88            .mls(mls)
89            .build()
90            .map_err(|err| format_err!(err))?;
91        ensure!(pure_order.verify(), "Invalid `PureOrder`.");
92        Ok(pure_order)
93    }
94
95    /// Constructs a new [`PureOrder`] structure for a specified rank with increasing-$`m`$ order.
96    /// The spatial inversion parity is determined by the parity of the deduced $`l`$ value.
97    ///
98    /// # Arguments
99    ///
100    /// * `lpure` - The required pure Gaussian rank.
101    ///
102    /// # Returns
103    ///
104    /// A [`PureOrder`] struct for a specified rank with increasing-$`m`$ order.
105    #[must_use]
106    pub fn increasingm(lpure: u32) -> Self {
107        let lpure_i32 = i32::try_from(lpure).expect("`lpure` cannot be converted to `i32`.");
108        let mls = (-lpure_i32..=lpure_i32).collect_vec();
109        Self::builder()
110            .lpure(lpure)
111            .mls(&mls)
112            .build()
113            .expect("Unable to construct a `PureOrder` structure with increasing-m order.")
114    }
115
116    /// Constructs a new [`PureOrder`] structure for a specified rank with decreasing-$`m`$ order.
117    /// The spatial inversion parity is determined by the parity of the deduced $`l`$ value.
118    ///
119    /// # Arguments
120    ///
121    /// * `lpure` - The required pure Gaussian rank.
122    ///
123    /// # Returns
124    ///
125    /// A [`PureOrder`] struct for a specified rank with decreasing-$`m`$ order.
126    #[must_use]
127    pub fn decreasingm(lpure: u32) -> Self {
128        let lpure_i32 = i32::try_from(lpure).expect("`lpure` cannot be converted to `i32`.");
129        let mls = (-lpure_i32..=lpure_i32).rev().collect_vec();
130        Self::builder()
131            .lpure(lpure)
132            .mls(&mls)
133            .build()
134            .expect("Unable to construct a `PureOrder` structure with decreasing-m order.")
135    }
136
137    /// Constructs a new [`PureOrder`] structure for a specified rank with Molden order. The spatial
138    /// inversion parity is determined by the parity of the deduced $`l`$ value.
139    ///
140    /// # Arguments
141    ///
142    /// * `lpure` - The required pure Gaussian rank.
143    ///
144    /// # Returns
145    ///
146    /// A [`PureOrder`] struct for a specified rank with Molden order.
147    #[must_use]
148    pub fn molden(lpure: u32) -> Self {
149        let lpure_i32 = i32::try_from(lpure).expect("`lpure` cannot be converted to `i32`.");
150        let mls = (0..=lpure_i32)
151            .flat_map(|absm| {
152                if absm == 0 {
153                    vec![0]
154                } else {
155                    vec![absm, -absm]
156                }
157            })
158            .collect_vec();
159        Self::builder()
160            .lpure(lpure)
161            .mls(&mls)
162            .build()
163            .expect("Unable to construct a `PureOrder` structure with Molden order.")
164    }
165
166    /// Verifies if this [`PureOrder`] struct is valid.
167    ///
168    /// # Returns
169    ///
170    /// A boolean indicating if this [`PureOrder`] struct is valid.
171    #[must_use]
172    pub fn verify(&self) -> bool {
173        let mls_set = self.mls.iter().collect::<HashSet<_>>();
174        let lpure = self.lpure;
175        mls_set.len() == self.ncomps() && mls_set.iter().all(|m| m.unsigned_abs() <= lpure)
176    }
177
178    /// Iterates over the constituent $`m_l`$ values.
179    pub fn iter(&'_ self) -> Iter<'_, i32> {
180        self.mls.iter()
181    }
182
183    /// Returns the number of pure components in the shell.
184    pub fn ncomps(&self) -> usize {
185        let lpure = usize::try_from(self.lpure).unwrap_or_else(|_| {
186            panic!(
187                "Unable to convert the pure degree {} to `usize`.",
188                self.lpure
189            )
190        });
191        2 * lpure + 1
192    }
193
194    /// Returns the $`m`$ value with a specified index in this shell.
195    pub fn get_m_with_index(&self, i: usize) -> Option<i32> {
196        self.mls.get(i).cloned()
197    }
198}
199
200impl fmt::Display for PureOrder {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        writeln!(f, "Pure rank: {}", self.lpure)?;
203        writeln!(f, "Order:")?;
204        for m in self.iter() {
205            writeln!(f, "  {m}")?;
206        }
207        Ok(())
208    }
209}
210
211impl fmt::Debug for PureOrder {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        writeln!(f, "Pure rank: {}", self.lpure)?;
214        writeln!(f, "Order:")?;
215        for m in self.iter() {
216            writeln!(f, "  {m:?}")?;
217        }
218        Ok(())
219    }
220}
221
222impl PermutableCollection for PureOrder {
223    type Rank = usize;
224
225    fn get_perm_of(&self, other: &Self) -> Option<Permutation<Self::Rank>> {
226        let o_mls: HashMap<&i32, usize> = other
227            .mls
228            .iter()
229            .enumerate()
230            .map(|(i, o_m)| (o_m, i))
231            .collect();
232        let image_opt: Option<Vec<Self::Rank>> =
233            self.mls.iter().map(|s_m| o_mls.get(s_m).copied()).collect();
234        image_opt.and_then(|image| Permutation::from_image(image).ok())
235    }
236
237    fn permute(&self, perm: &Permutation<Self::Rank>) -> Result<Self, anyhow::Error> {
238        let mut p_pureorder = self.clone();
239        p_pureorder.permute_mut(perm)?;
240        Ok(p_pureorder)
241    }
242
243    fn permute_mut(&mut self, perm: &Permutation<Self::Rank>) -> Result<(), anyhow::Error> {
244        permute_inplace(&mut self.mls, perm);
245        Ok(())
246    }
247}
248
249// ~~~~~~~~~
250// CartOrder
251// ~~~~~~~~~
252
253/// Structure to contain information about the ordering of Cartesian Gaussians of a certain rank.
254#[derive(Clone, Builder, PartialEq, Eq, Hash)]
255pub struct CartOrder {
256    /// A sequence of $`(l_x, l_y, l_z)`$ tuples giving the ordering of the Cartesian Gaussians.
257    #[builder(setter(custom))]
258    pub cart_tuples: Vec<(u32, u32, u32)>,
259
260    /// The rank of the Cartesian Gaussians.
261    pub lcart: u32,
262}
263
264impl CartOrderBuilder {
265    fn cart_tuples(&mut self, cart_tuples: &[(u32, u32, u32)]) -> &mut Self {
266        let lcart = self.lcart.expect("`lcart` has not been set.");
267        assert!(
268            cart_tuples.iter().all(|(lx, ly, lz)| lx + ly + lz == lcart),
269            "Inconsistent total Cartesian orders between components."
270        );
271        assert_eq!(
272            cart_tuples.len(),
273            ((lcart + 1) * (lcart + 2)).div_euclid(2) as usize,
274            "Unexpected number of components for `lcart` = {}.",
275            lcart
276        );
277        self.cart_tuples = Some(cart_tuples.to_vec());
278        self
279    }
280}
281
282impl CartOrder {
283    /// Returns a builder to construct a new [`CartOrder`] structure.
284    fn builder() -> CartOrderBuilder {
285        CartOrderBuilder::default()
286    }
287
288    /// Constructs a new [`CartOrder`] structure from its constituting tuples, each of which contains
289    /// the $`x`$, $`y`$, and $`z`$ exponents for one Cartesian term.
290    ///
291    /// # Errors
292    ///
293    /// Errors if the Cartesian tuples are invalid (*e.g.* missing components or containing
294    /// inconsistent components).
295    pub fn new(cart_tuples: &[(u32, u32, u32)]) -> Result<Self, anyhow::Error> {
296        let first_tuple = cart_tuples
297            .get(0)
298            .ok_or(format_err!("No Cartesian tuples found."))?;
299        let lcart = first_tuple.0 + first_tuple.1 + first_tuple.2;
300        let cart_order = CartOrder::builder()
301            .lcart(lcart)
302            .cart_tuples(cart_tuples)
303            .build()
304            .map_err(|err| format_err!(err))?;
305        ensure!(cart_order.verify(), "Invalid `CartOrder`.");
306        Ok(cart_order)
307    }
308
309    /// Constructs a new [`CartOrder`] structure for a specified rank with lexicographic order.
310    ///
311    /// # Arguments
312    ///
313    /// * `lcart` - The required Cartesian Gaussian rank.
314    ///
315    /// # Returns
316    ///
317    /// A [`CartOrder`] struct for a specified rank with lexicographic order.
318    #[must_use]
319    pub fn lex(lcart: u32) -> Self {
320        let mut cart_tuples =
321            Vec::with_capacity(((lcart + 1) * (lcart + 2)).div_euclid(2) as usize);
322        for lx in (0..=lcart).rev() {
323            for ly in (0..=(lcart - lx)).rev() {
324                cart_tuples.push((lx, ly, lcart - lx - ly));
325            }
326        }
327        Self::builder()
328            .lcart(lcart)
329            .cart_tuples(&cart_tuples)
330            .build()
331            .expect("Unable to construct a `CartOrder` structure with lexicographic order.")
332    }
333
334    /// Constructs a new [`CartOrder`] structure for a specified rank with Molden order.
335    ///
336    /// # Arguments
337    ///
338    /// * `lcart` - The required Cartesian Gaussian rank up to 4.
339    ///
340    /// # Returns
341    ///
342    /// A [`CartOrder`] struct for a specified rank with Q-Chem order.
343    ///
344    /// # Panics
345    ///
346    /// Panics if `lcart` is greater than 4.
347    #[must_use]
348    pub fn molden(lcart: u32) -> Self {
349        assert!(lcart <= 4, "`lcart` > 4 is not specified by Molden.");
350        let cart_tuples: Vec<(u32, u32, u32)> = match lcart {
351            0 => vec![(0, 0, 0)],
352            1 => vec![(1, 0, 0), (0, 1, 0), (0, 0, 1)],
353            2 => vec![
354                (2, 0, 0),
355                (0, 2, 0),
356                (0, 0, 2),
357                (1, 1, 0),
358                (1, 0, 1),
359                (0, 1, 1),
360            ],
361            3 => vec![
362                (3, 0, 0),
363                (0, 3, 0),
364                (0, 0, 3),
365                (1, 2, 0),
366                (2, 1, 0),
367                (2, 0, 1),
368                (1, 0, 2),
369                (0, 1, 2),
370                (0, 2, 1),
371                (1, 1, 1),
372            ],
373            4 => vec![
374                (4, 0, 0),
375                (0, 4, 0),
376                (0, 0, 4),
377                (3, 1, 0),
378                (3, 0, 1),
379                (1, 3, 0),
380                (0, 3, 1),
381                (1, 0, 3),
382                (0, 1, 3),
383                (2, 2, 0),
384                (2, 0, 2),
385                (0, 2, 2),
386                (2, 1, 1),
387                (1, 2, 1),
388                (1, 1, 2),
389            ],
390            _ => panic!("`lcart` > 4 is not specified by Molden."),
391        };
392        Self::builder()
393            .lcart(lcart)
394            .cart_tuples(&cart_tuples)
395            .build()
396            .expect("Unable to construct a `CartOrder` structure with Molden order.")
397    }
398
399    /// Constructs a new [`CartOrder`] structure for a specified rank with Q-Chem order.
400    ///
401    /// # Arguments
402    ///
403    /// * `lcart` - The required Cartesian Gaussian rank.
404    ///
405    /// # Returns
406    ///
407    /// A [`CartOrder`] struct for a specified rank with Q-Chem order.
408    #[must_use]
409    pub fn qchem(lcart: u32) -> Self {
410        let cart_tuples: Vec<(u32, u32, u32)> = if lcart > 0 {
411            (0..3)
412                .product_repeat(lcart as usize)
413                .filter_map(|tup| {
414                    let mut tup_sorted = tup.clone();
415                    tup_sorted.sort_unstable();
416                    tup_sorted.reverse();
417                    if tup == tup_sorted {
418                        let lcartqns = tup.iter().collect::<Counter<_>>();
419                        Some((
420                            <usize as TryInto<u32>>::try_into(*(lcartqns.get(&0).unwrap_or(&0)))
421                                .expect("Unable to convert Cartesian x-exponent to `u32`."),
422                            <usize as TryInto<u32>>::try_into(*(lcartqns.get(&1).unwrap_or(&0)))
423                                .expect("Unable to convert Cartesian y-exponent to `u32`."),
424                            <usize as TryInto<u32>>::try_into(*(lcartqns.get(&2).unwrap_or(&0)))
425                                .expect("Unable to convert Cartesian z-exponent to `u32`."),
426                        ))
427                    } else {
428                        None
429                    }
430                })
431                .collect()
432        } else {
433            vec![(0, 0, 0)]
434        };
435        Self::builder()
436            .lcart(lcart)
437            .cart_tuples(&cart_tuples)
438            .build()
439            .expect("Unable to construct a `CartOrder` structure with Q-Chem order.")
440    }
441
442    /// Verifies if this [`CartOrder`] struct is valid.
443    ///
444    /// # Returns
445    ///
446    /// A boolean indicating if this [`CartOrder`] struct is valid.
447    #[must_use]
448    pub fn verify(&self) -> bool {
449        let cart_tuples_set = self.cart_tuples.iter().collect::<HashSet<_>>();
450        let lcart = self.lcart;
451        cart_tuples_set.len() == self.ncomps()
452            && cart_tuples_set
453                .iter()
454                .all(|(lx, ly, lz)| lx + ly + lz == lcart)
455    }
456
457    /// Iterates over the constituent tuples.
458    pub fn iter(&'_ self) -> Iter<'_, (u32, u32, u32)> {
459        self.cart_tuples.iter()
460    }
461
462    /// Returns the number of Cartesian components in the shell.
463    pub fn ncomps(&self) -> usize {
464        let lcart = usize::try_from(self.lcart).unwrap_or_else(|_| {
465            panic!(
466                "Unable to convert the Cartesian degree {} to `usize`.",
467                self.lcart
468            )
469        });
470        ((lcart + 1) * (lcart + 2)).div_euclid(2)
471    }
472
473    /// Returns the Cartesian component with a specified index in this shell.
474    pub fn get_cart_tuple_with_index(&self, i: usize) -> Option<(u32, u32, u32)> {
475        self.cart_tuples.get(i).cloned()
476    }
477}
478
479impl fmt::Display for CartOrder {
480    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
481        writeln!(f, "Cartesian rank: {}", self.lcart)?;
482        writeln!(f, "Order:")?;
483        for cart_tuple in self.iter() {
484            writeln!(f, "  {}", cart_tuple_to_str(cart_tuple, true))?;
485        }
486        Ok(())
487    }
488}
489
490impl fmt::Debug for CartOrder {
491    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492        writeln!(f, "Cartesian rank: {}", self.lcart)?;
493        writeln!(f, "Order:")?;
494        for cart_tuple in self.iter() {
495            writeln!(f, "  {cart_tuple:?}")?;
496        }
497        Ok(())
498    }
499}
500
501impl PermutableCollection for CartOrder {
502    type Rank = usize;
503
504    fn get_perm_of(&self, other: &Self) -> Option<Permutation<Self::Rank>> {
505        let o_cart_tuples: HashMap<&(u32, u32, u32), usize> = other
506            .cart_tuples
507            .iter()
508            .enumerate()
509            .map(|(i, o_cart_tuple)| (o_cart_tuple, i))
510            .collect();
511        let image_opt: Option<Vec<Self::Rank>> = self
512            .cart_tuples
513            .iter()
514            .map(|s_cart_tuple| o_cart_tuples.get(s_cart_tuple).copied())
515            .collect();
516        image_opt.and_then(|image| Permutation::from_image(image).ok())
517    }
518
519    fn permute(&self, perm: &Permutation<Self::Rank>) -> Result<Self, anyhow::Error> {
520        let mut p_cartorder = self.clone();
521        p_cartorder.permute_mut(perm)?;
522        Ok(p_cartorder)
523    }
524
525    fn permute_mut(&mut self, perm: &Permutation<Self::Rank>) -> Result<(), anyhow::Error> {
526        permute_inplace(&mut self.cart_tuples, perm);
527        Ok(())
528    }
529}
530
531/// Translates a Cartesian exponent tuple to a human-understandable string.
532///
533/// # Arguments
534///
535/// * `cart_tuple` - A tuple of $`(l_x, l_y, l_z)`$ specifying the exponents of the Cartesian
536/// components of the Cartesian Gaussian.
537/// * flat - A flag indicating if the string representation is flat (*e.g.* `xxyz`) or compact
538/// (*e.g.* `x^2yz`).
539///
540/// Returns
541///
542/// The string representation of the Cartesian exponent tuple.
543pub(crate) fn cart_tuple_to_str(cart_tuple: &(u32, u32, u32), flat: bool) -> String {
544    if cart_tuple.0 + cart_tuple.1 + cart_tuple.2 == 0u32 {
545        "1".to_string()
546    } else {
547        let cart_array = [cart_tuple.0, cart_tuple.1, cart_tuple.2];
548        let carts = ["x", "y", "z"];
549        Itertools::intersperse(
550            cart_array.iter().enumerate().map(|(i, &l)| {
551                if flat {
552                    carts[i].repeat(l as usize)
553                } else {
554                    match l.cmp(&1) {
555                        Ordering::Greater => format!("{}^{l}", carts[i]),
556                        Ordering::Equal => carts[i].to_string(),
557                        Ordering::Less => String::new(),
558                    }
559                }
560            }),
561            String::new(),
562        )
563        .collect::<String>()
564    }
565}
566
567// ~~~~~~~~~~~
568// SpinorOrder
569// ~~~~~~~~~~~
570
571// SpinorParticleType enum
572// .......................
573
574/// Enumerated type for spinor particle types.
575#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
576pub enum SpinorParticleType {
577    /// Variant for spinors describing fermionic particles. The optional associated value contains
578    /// any additional balance symmetry to be applied to the spinors.
579    Fermion(Option<SpinorBalanceSymmetry>),
580
581    /// Variant for spinors describing antifermionic particles. The optional associated value
582    /// contains any additional balance symmetry to be applied to the spinors.
583    Antifermion(Option<SpinorBalanceSymmetry>),
584}
585
586impl fmt::Display for SpinorParticleType {
587    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
588        match self {
589            SpinorParticleType::Fermion(None) => {
590                write!(f, "fermion")
591            }
592            SpinorParticleType::Fermion(Some(sbs)) => {
593                write!(f, "fermion ({sbs})")
594            }
595            SpinorParticleType::Antifermion(None) => {
596                write!(f, "antifermion")
597            }
598            SpinorParticleType::Antifermion(Some(sbs)) => {
599                write!(f, "antifermion ({sbs})")
600            }
601        }
602    }
603}
604
605// SpinorBalanceSymmetry enum
606// ..........................
607
608/// Enumerated type for spinor balance symmetry types.
609#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
610pub enum SpinorBalanceSymmetry {
611    /// Variant for kinetic balance, σ·p.
612    KineticBalance,
613}
614
615impl fmt::Display for SpinorBalanceSymmetry {
616    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
617        match self {
618            SpinorBalanceSymmetry::KineticBalance => {
619                write!(f, "σ·p")
620            }
621        }
622    }
623}
624
625/// Structure to contain information about the ordering of spinors of a certain rank.
626#[derive(Clone, Builder, PartialEq, Eq, Hash)]
627pub struct SpinorOrder {
628    /// The angular momentum (times two) of the spinor Gaussians. This must be an odd integer.
629    #[builder(setter(custom))]
630    pub two_j: u32,
631
632    /// A sequence of $`2m_j`$ values giving the ordering of the spinor Gaussians.
633    #[builder(setter(custom))]
634    two_mjs: Vec<i32>,
635
636    /// The spatial inversion parity of the spinor Gaussians: `true` if even under spatial inversion
637    /// and `false` if odd. This is intrinsic to the spinor Gaussians and independent of the
638    /// particle type and any associated balance symmetry.
639    pub even: bool,
640
641    /// The particle type described by this spinor shell.
642    pub particle_type: SpinorParticleType,
643}
644
645impl SpinorOrderBuilder {
646    fn two_j(&mut self, two_j: u32) -> &mut Self {
647        if two_j.rem_euclid(2) != 1 {
648            panic!("`two_j` must be odd.")
649        }
650        self.two_j = Some(two_j);
651        self
652    }
653
654    fn two_mjs(&mut self, two_mjs: &[i32]) -> &mut Self {
655        let two_j = self.two_j.expect("`two_j` has not been set.");
656        let max_two_m = two_mjs
657            .iter()
658            .map(|two_m| two_m.unsigned_abs())
659            .max()
660            .expect("The maximum |2m| value could not be determined.");
661        assert_eq!(
662            max_two_m, two_j,
663            "The maximum |2m| value does not equal 2j = {two_j}."
664        );
665        assert_eq!(
666            two_mjs.len(),
667            (two_j + 1) as usize,
668            "The number of 2m values specified does not equal 2j + 1 = {}",
669            two_j + 1
670        );
671        self.two_mjs = Some(two_mjs.to_vec());
672        self
673    }
674}
675
676impl SpinorOrder {
677    /// Returns a builder to construct a new [`SpinorOrder`] structure.
678    fn builder() -> SpinorOrderBuilder {
679        SpinorOrderBuilder::default()
680    }
681
682    /// Constructs a new [`SpinorOrder`] structure from its constituting $`2m_j`$ values and a
683    /// specified spatial parity.
684    pub fn new(
685        two_mjs: &[i32],
686        even: bool,
687        particle_type: SpinorParticleType,
688    ) -> Result<Self, anyhow::Error> {
689        let two_j = two_mjs
690            .iter()
691            .map(|two_m| two_m.unsigned_abs())
692            .max()
693            .ok_or_else(|| format_err!("The maximum |2m| value could not be determined."))?;
694        let spinor_order = SpinorOrder::builder()
695            .two_j(two_j)
696            .two_mjs(two_mjs)
697            .even(even)
698            .particle_type(particle_type)
699            .build()
700            .map_err(|err| format_err!(err))?;
701        ensure!(spinor_order.verify(), "Invalid `SpinorOrder`.");
702        Ok(spinor_order)
703    }
704
705    /// Constructs a new [`SpinorOrder`] structure for a specified angular momentum with
706    /// increasing-$`m`$ order.
707    ///
708    /// # Arguments
709    ///
710    /// * `two_j` - The required spinor angular momentum (times two).
711    /// * `even` - Boolean indicating whether the large-component spinors are even with respect to
712    /// spatial inversion.
713    /// * `particle_type` - The associated particle type.
714    ///
715    /// # Returns
716    ///
717    /// A [`SpinorOrder`] struct for a specified angular momentum with increasing-$`m`$ order.
718    #[must_use]
719    pub fn increasingm(two_j: u32, even: bool, particle_type: SpinorParticleType) -> Self {
720        let two_j_i32 = i32::try_from(two_j).expect("`two_j` cannot be converted to `i32`.");
721        let two_mjs = (-two_j_i32..=two_j_i32).step_by(2).collect_vec();
722        Self::builder()
723            .two_j(two_j)
724            .two_mjs(&two_mjs)
725            .even(even)
726            .particle_type(particle_type)
727            .build()
728            .expect("Unable to construct a `SpinorOrder` structure with increasing-m order.")
729    }
730
731    /// Constructs a new [`SpinorOrder`] structure for a specified angular momentum with
732    /// decreasing-$`m`$ order.
733    ///
734    /// # Arguments
735    ///
736    /// * `two_j` - The required spinor angular momentum (times two).
737    /// * `even` - Boolean indicating whether the large-component spinors are even with respect to
738    /// spatial inversion.
739    /// * `particle_type` - The associated particle type.
740    ///
741    /// # Returns
742    ///
743    /// A [`SpinorOrder`] struct for a specified angular momentum with decreasing-$`m`$ order.
744    #[must_use]
745    pub fn decreasingm(two_j: u32, even: bool, particle_type: SpinorParticleType) -> Self {
746        let two_j_i32 = i32::try_from(two_j).expect("`two_j` cannot be converted to `i32`.");
747        let two_mjs = (-two_j_i32..=two_j_i32).rev().step_by(2).collect_vec();
748        Self::builder()
749            .two_j(two_j)
750            .two_mjs(&two_mjs)
751            .even(even)
752            .particle_type(particle_type)
753            .build()
754            .expect("Unable to construct a `SpinorOrder` structure with decreasing-m order.")
755    }
756
757    /// Constructs a new [`SpinorOrder`] structure for a specified angular momentum with Molden order.
758    ///
759    /// # Arguments
760    ///
761    /// * `two_j` - The required spinor angular momentum (times two).
762    /// * `even` - Boolean indicating whether the large-component spinors are even with respect to
763    /// spatial inversion.
764    /// * `particle_type` - The associated particle type.
765    ///
766    /// # Returns
767    ///
768    /// A [`SpinorOrder`] struct for a specified angular momentum with Molden order.
769    #[must_use]
770    pub fn molden(two_j: u32, even: bool, particle_type: SpinorParticleType) -> Self {
771        let two_j_i32 = i32::try_from(two_j).expect("`two_j` cannot be converted to `i32`.");
772        let two_mjs = (1..=two_j_i32)
773            .step_by(2)
774            .flat_map(|abs_twom| vec![abs_twom, -abs_twom])
775            .collect_vec();
776        Self::builder()
777            .two_j(two_j)
778            .two_mjs(&two_mjs)
779            .even(even)
780            .particle_type(particle_type)
781            .build()
782            .expect("Unable to construct a `SpinorOrder` structure with Molden order.")
783    }
784
785    /// Verifies if this [`SpinorOrder`] struct is valid.
786    ///
787    /// # Returns
788    ///
789    /// A boolean indicating if this [`SpinorOrder`] struct is valid.
790    #[must_use]
791    pub fn verify(&self) -> bool {
792        let two_mjs_set = self.two_mjs.iter().collect::<HashSet<_>>();
793        let two_j = self.two_j;
794        two_mjs_set.len() == self.ncomps()
795            && two_mjs_set
796                .iter()
797                .all(|two_m| two_m.unsigned_abs() <= two_j)
798    }
799
800    /// Iterates over the constituent $`2m_j`$ values.
801    pub fn iter(&'_ self) -> Iter<'_, i32> {
802        self.two_mjs.iter()
803    }
804
805    /// Returns the number of pure components in the shell.
806    pub fn ncomps(&self) -> usize {
807        let two_j = usize::try_from(self.two_j).unwrap_or_else(|_| {
808            panic!(
809                "Unable to convert the two-j value {} to `usize`.",
810                self.two_j
811            )
812        });
813        two_j + 1
814    }
815
816    /// Returns the $`2m`$ value with a specified index in this shell.
817    pub fn get_two_m_with_index(&self, i: usize) -> Option<i32> {
818        self.two_mjs.get(i).cloned()
819    }
820
821    /// Returns the angular momentum quantum number of the spatial part of the spinors in this
822    /// shell.
823    pub fn l(&self) -> u32 {
824        if self.two_j.rem_euclid(4) == 1 {
825            // even l is to the left of j, and odd l to the right
826            if self.even {
827                (self.two_j - 1).div_euclid(2)
828            } else {
829                (self.two_j + 1).div_euclid(2)
830            }
831        } else {
832            assert_eq!(self.two_j.rem_euclid(4), 3);
833            // even l is to the right of j, and odd l to the left
834            if self.even {
835                (self.two_j + 1).div_euclid(2)
836            } else {
837                (self.two_j - 1).div_euclid(2)
838            }
839        }
840    }
841
842    /// Returns the value of $`a = 2(j - l)`$.
843    pub fn a(&self) -> i8 {
844        if self.two_j.rem_euclid(4) == 1 {
845            // even l is to the left of j, and odd l to the right
846            if self.even { 1 } else { -1 }
847        } else {
848            assert_eq!(self.two_j.rem_euclid(4), 3);
849            // even l is to the right of j, and odd l to the left
850            if self.even { -1 } else { 1 }
851        }
852    }
853}
854
855impl fmt::Display for SpinorOrder {
856    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
857        writeln!(
858            f,
859            "Angular momentum: {}/2 ({}; l = {}), {}",
860            self.two_j,
861            if self.a() == 1 { "+" } else { "-" },
862            self.l(),
863            self.particle_type,
864        )?;
865        writeln!(f, "Order:")?;
866        for two_m in self.iter() {
867            writeln!(f, "  {two_m}/2")?;
868        }
869        Ok(())
870    }
871}
872
873impl fmt::Debug for SpinorOrder {
874    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
875        writeln!(
876            f,
877            "Angular momentum: {}/2 ({}; l = {}), {}",
878            self.two_j,
879            if self.a() == 1 { "+" } else { "-" },
880            self.l(),
881            self.particle_type,
882        )?;
883        writeln!(f, "Order:")?;
884        for two_m in self.iter() {
885            writeln!(f, "  {two_m:?}/2")?;
886        }
887        Ok(())
888    }
889}
890
891impl PermutableCollection for SpinorOrder {
892    type Rank = usize;
893
894    fn get_perm_of(&self, other: &Self) -> Option<Permutation<Self::Rank>> {
895        let o_two_mjs: HashMap<&i32, usize> = other
896            .two_mjs
897            .iter()
898            .enumerate()
899            .map(|(i, o_two_m)| (o_two_m, i))
900            .collect();
901        let image_opt: Option<Vec<Self::Rank>> = self
902            .two_mjs
903            .iter()
904            .map(|s_two_m| o_two_mjs.get(s_two_m).copied())
905            .collect();
906        image_opt.and_then(|image| Permutation::from_image(image).ok())
907    }
908
909    fn permute(&self, perm: &Permutation<Self::Rank>) -> Result<Self, anyhow::Error> {
910        let mut p_pureorder = self.clone();
911        p_pureorder.permute_mut(perm)?;
912        Ok(p_pureorder)
913    }
914
915    fn permute_mut(&mut self, perm: &Permutation<Self::Rank>) -> Result<(), anyhow::Error> {
916        permute_inplace(&mut self.two_mjs, perm);
917        Ok(())
918    }
919}
920
921// ----------
922// ShellOrder
923// ----------
924
925/// Enumerated type to indicate the type of the angular functions in a shell and how they are
926/// ordered.
927#[derive(Clone, PartialEq, Eq, Hash, Debug)]
928pub enum ShellOrder {
929    /// This variant indicates that the angular functions are real solid harmonics. The associated
930    /// value is a [`PureOrder`] struct containing the order of these functions.
931    Pure(PureOrder),
932
933    /// This variant indicates that the angular functions are Cartesian functions. The associated
934    /// value is a [`CartOrder`] struct containing the order of these functions.
935    Cart(CartOrder),
936
937    /// This variant indicates that the angular functions are spinors. The associated
938    /// value is a [`SpinorOrder`] struct containing the order of these functions.
939    Spinor(SpinorOrder),
940}
941
942impl fmt::Display for ShellOrder {
943    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
944        match self {
945            ShellOrder::Pure(pure_order) => write!(
946                f,
947                "Pure ({}) ({})",
948                if pure_order.even { "g" } else { "u" },
949                pure_order.iter().map(|m| m.to_string()).join(", ")
950            ),
951            ShellOrder::Cart(cart_order) => write!(
952                f,
953                "Cart ({}) ({})",
954                if cart_order.lcart % 2 == 0 { "g" } else { "u" },
955                cart_order
956                    .iter()
957                    .map(|cart_tuple| { cart_tuple_to_str(cart_tuple, true) })
958                    .join(", ")
959            ),
960            ShellOrder::Spinor(spinor_order) => write!(
961                f,
962                "Spinor ({}; l = {}), {}, ({})",
963                if spinor_order.a() == 1 { "+" } else { "-" },
964                spinor_order.l(),
965                spinor_order.particle_type,
966                spinor_order
967                    .iter()
968                    .map(|two_m| format!("{two_m}/2"))
969                    .join(", ")
970            ),
971        }
972    }
973}
974
975// ----------
976// BasisShell
977// ----------
978
979/// Structure representing a shell in an atomic-orbital basis set.
980#[derive(Clone, Builder, PartialEq, Eq, Hash, Debug)]
981pub struct BasisShell {
982    /// A non-negative integer indicating the rank of the shell. If this shell is pure, the rank
983    /// equals its angular momentum. If this shell is Cartesian, the rank is the sum of the
984    /// exponents of the Cartesian coordinates. If this shell is a spinor, the rank is twice its
985    /// angular momentum.
986    #[builder(setter(custom))]
987    pub l: u32,
988
989    /// An enum indicating the type of the angular functions in a shell and how they are ordered.
990    #[builder(setter(custom))]
991    pub shell_order: ShellOrder,
992}
993
994impl BasisShellBuilder {
995    fn l(&mut self, l: u32) -> &mut Self {
996        match self.shell_order.as_ref() {
997            Some(ShellOrder::Pure(pure_order)) => assert_eq!(pure_order.lpure, l),
998            Some(ShellOrder::Cart(cart_order)) => assert_eq!(cart_order.lcart, l),
999            Some(ShellOrder::Spinor(spinor_order)) => assert_eq!(spinor_order.two_j, l),
1000            None => {}
1001        }
1002        self.l = Some(l);
1003        self
1004    }
1005
1006    fn shell_order(&mut self, shl_ord: ShellOrder) -> &mut Self {
1007        match (&shl_ord, self.l) {
1008            (ShellOrder::Pure(pure_order), Some(l)) => assert_eq!(pure_order.lpure, l),
1009            (ShellOrder::Cart(cart_order), Some(l)) => assert_eq!(cart_order.lcart, l),
1010            (ShellOrder::Spinor(spinor_order), Some(l)) => assert_eq!(spinor_order.two_j, l),
1011            _ => {}
1012        }
1013        self.shell_order = Some(shl_ord);
1014        self
1015    }
1016}
1017
1018impl BasisShell {
1019    /// Returns a builder to construct a new [`BasisShell`].
1020    ///
1021    /// # Returns
1022    ///
1023    /// A builder to construct a new [`BasisShell`].
1024    fn builder() -> BasisShellBuilder {
1025        BasisShellBuilder::default()
1026    }
1027
1028    /// Constructs a new [`BasisShell`].
1029    ///
1030    /// # Arguments
1031    ///
1032    /// * `l` - The rank of this shell, which is equal to $`l_{\mathrm{pure}}`$ for a pure shell,
1033    /// $`l_{\mathrm{cart}}`$ for a Cartesian shell, or $`2j`$ for a spinor shell.
1034    /// * `shl_ord` - A [`ShellOrder`] structure specifying the type and ordering of the basis
1035    /// functions in this shell.
1036    pub fn new(l: u32, shl_ord: ShellOrder) -> Self {
1037        match &shl_ord {
1038            ShellOrder::Pure(pureorder) => assert_eq!(pureorder.lpure, l),
1039            ShellOrder::Cart(cartorder) => assert_eq!(cartorder.lcart, l),
1040            ShellOrder::Spinor(spinororder) => assert_eq!(spinororder.two_j, l),
1041        }
1042        BasisShell::builder()
1043            .l(l)
1044            .shell_order(shl_ord)
1045            .build()
1046            .expect("Unable to construct a `BasisShell`.")
1047    }
1048
1049    /// The number of basis functions in this shell.
1050    pub fn n_funcs(&self) -> usize {
1051        let lsize = self.l as usize;
1052        match self.shell_order {
1053            ShellOrder::Pure(_) => 2 * lsize + 1,
1054            ShellOrder::Cart(_) => ((lsize + 1) * (lsize + 2)).div_euclid(2),
1055            ShellOrder::Spinor(_) => lsize + 1,
1056        }
1057    }
1058}
1059
1060// ---------
1061// BasisAtom
1062// ---------
1063
1064/// Structure containing the ordered sequence of the shells for an atom.
1065#[derive(Clone, Builder, PartialEq, Eq, Hash, Debug)]
1066pub struct BasisAtom<'a> {
1067    /// An atom in the basis set.
1068    pub(crate) atom: &'a Atom,
1069
1070    /// The ordered shells associated with this atom.
1071    #[builder(setter(custom))]
1072    pub(crate) basis_shells: Vec<BasisShell>,
1073}
1074
1075impl<'a> BasisAtomBuilder<'a> {
1076    pub(crate) fn basis_shells(&mut self, bss: &[BasisShell]) -> &mut Self {
1077        self.basis_shells = Some(bss.to_vec());
1078        self
1079    }
1080}
1081
1082impl<'a> BasisAtom<'a> {
1083    /// Returns a builder to construct a new [`BasisAtom`].
1084    ///
1085    /// # Returns
1086    ///
1087    /// A builder to construct a new [`BasisAtom`].
1088    pub(crate) fn builder() -> BasisAtomBuilder<'a> {
1089        BasisAtomBuilder::default()
1090    }
1091
1092    /// Constructs a new [`BasisAtom`].
1093    ///
1094    /// # Arguments
1095    ///
1096    /// * `atm` - A reference to an atom.
1097    /// * `bss` - A sequence of [`BasisShell`]s containing the basis functions localised on this
1098    /// atom.
1099    pub fn new(atm: &'a Atom, bss: &[BasisShell]) -> Self {
1100        BasisAtom::builder()
1101            .atom(atm)
1102            .basis_shells(bss)
1103            .build()
1104            .expect("Unable to construct a `BasisAtom`.")
1105    }
1106
1107    /// The number of basis functions localised on this atom.
1108    fn n_funcs(&self) -> usize {
1109        self.basis_shells.iter().map(BasisShell::n_funcs).sum()
1110    }
1111
1112    /// The ordered tuples of 0-based indices indicating the starting (inclusive) and ending
1113    /// (exclusive) positions of the shells on this atom.
1114    fn shell_boundary_indices(&self) -> Vec<(usize, usize)> {
1115        self.basis_shells
1116            .iter()
1117            .scan(0, |acc, basis_shell| {
1118                let start_index = *acc;
1119                *acc += basis_shell.n_funcs();
1120                Some((start_index, *acc))
1121            })
1122            .collect::<Vec<_>>()
1123    }
1124}
1125
1126// -----------------
1127// BasisAngularOrder
1128// -----------------
1129
1130/// Structure containing the angular momentum information of an atomic-orbital basis set that is
1131/// required for symmetry transformation to be performed.
1132#[derive(Clone, Builder)]
1133pub struct BasisAngularOrder<'a> {
1134    /// An ordered sequence of [`BasisAtom`] in the order the atoms are defined in the molecule.
1135    #[builder(setter(custom))]
1136    pub(crate) basis_atoms: Vec<BasisAtom<'a>>,
1137}
1138
1139impl<'a> BasisAngularOrderBuilder<'a> {
1140    pub(crate) fn basis_atoms(&mut self, batms: &[BasisAtom<'a>]) -> &mut Self {
1141        self.basis_atoms = Some(batms.to_vec());
1142        self
1143    }
1144}
1145
1146impl<'a> BasisAngularOrder<'a> {
1147    /// Returns a builder to construct a new [`BasisAngularOrder`].
1148    ///
1149    /// # Returns
1150    ///
1151    /// A builder to construct a new [`BasisAngularOrder`].
1152    #[must_use]
1153    pub(crate) fn builder() -> BasisAngularOrderBuilder<'a> {
1154        BasisAngularOrderBuilder::default()
1155    }
1156
1157    /// Constructs a new [`BasisAngularOrder`] structure from the constituting [`BasisAtom`]s
1158    /// without any additional balance symmetry auxiliary information.
1159    ///
1160    /// # Arguments
1161    ///
1162    /// * `batms` - The constituent [`BasisAtom`]s.
1163    pub fn new(batms: &[BasisAtom<'a>]) -> Self {
1164        BasisAngularOrder::builder()
1165            .basis_atoms(batms)
1166            .build()
1167            .expect("Unable to construct a `BasisAngularOrder`.")
1168    }
1169
1170    /// The number of atoms in the basis.
1171    pub fn n_atoms(&self) -> usize {
1172        self.basis_atoms.len()
1173    }
1174
1175    /// The number of basis functions in this basis.
1176    pub fn n_funcs(&self) -> usize {
1177        self.basis_atoms.iter().map(BasisAtom::n_funcs).sum()
1178    }
1179
1180    /// The ordered tuples of 0-based shell indices indicating the starting (inclusive) and ending
1181    /// (exclusive) shell positions of the atoms in this basis.
1182    pub fn atom_boundary_indices(&self) -> Vec<(usize, usize)> {
1183        self.basis_atoms
1184            .iter()
1185            .scan(0, |acc, basis_atom| {
1186                let start_index = *acc;
1187                *acc += basis_atom.n_funcs();
1188                Some((start_index, *acc))
1189            })
1190            .collect::<Vec<_>>()
1191    }
1192
1193    /// The ordered tuples of 0-based function indices indicating the starting (inclusive) and
1194    /// ending (exclusive) positions of the shells in this basis.
1195    pub fn shell_boundary_indices(&self) -> Vec<(usize, usize)> {
1196        let atom_boundary_indices = self.atom_boundary_indices();
1197        self.basis_atoms
1198            .iter()
1199            .zip(atom_boundary_indices)
1200            .flat_map(|(basis_atom, (atom_start, _))| {
1201                basis_atom
1202                    .shell_boundary_indices()
1203                    .iter()
1204                    .map(|(shell_start, shell_end)| {
1205                        (shell_start + atom_start, shell_end + atom_start)
1206                    })
1207                    .collect::<Vec<_>>()
1208            })
1209            .collect::<Vec<_>>()
1210    }
1211
1212    /// An iterator over the constituent [`BasisShell`]s in this basis.
1213    pub fn basis_shells(&self) -> impl Iterator<Item = &BasisShell> + '_ {
1214        self.basis_atoms
1215            .iter()
1216            .flat_map(|basis_atom| basis_atom.basis_shells.iter())
1217    }
1218
1219    /// Determines the permutation of the functions in this [`BasisAngularOrder`] to map `self` to
1220    /// `other`, given that the shells themselves remain unchanged while only the functions in each
1221    /// shell are permuted.
1222    ///
1223    /// For example, consider `self`:
1224    /// ```text
1225    /// S (1)
1226    /// P (x, y, z)
1227    /// D (xx, xy, xz, yy, yz, zz)
1228    /// ```
1229    ///
1230    /// and `other`:
1231    /// ```text
1232    /// S (1)
1233    /// P (y, z, x)
1234    /// D (xx, xy, yy, xz, yz, zz)
1235    /// ```
1236    ///
1237    /// the mapping permutation is given by `π(0, 3, 1, 2, 4, 5, 7, 6, 8, 9)`.
1238    ///
1239    /// # Arguments
1240    ///
1241    /// * `other` - Another [`BasisAngularOrder`] to be compared against.
1242    ///
1243    /// # Returns
1244    ///
1245    /// The mapping permutation, if any.
1246    pub(crate) fn get_perm_of_functions_fixed_shells(
1247        &self,
1248        other: &Self,
1249    ) -> Result<Permutation<usize>, anyhow::Error> {
1250        if self.n_funcs() == other.n_funcs() && self.n_atoms() == other.n_atoms() {
1251            let s_shell_boundaries = self.shell_boundary_indices();
1252            let o_shell_boundaries = other.shell_boundary_indices();
1253            if s_shell_boundaries.len() == o_shell_boundaries.len() {
1254                let image = izip!(
1255                    self.basis_shells(),
1256                    other.basis_shells(),
1257                    s_shell_boundaries.iter(),
1258                    o_shell_boundaries.iter()
1259                )
1260                .map(|(s_bs, o_bs, (s_start, s_end), (o_start, o_end))| {
1261                    if (s_start, s_end) == (o_start, o_end) {
1262                        let s_shl_ord = &s_bs.shell_order;
1263                        let o_shl_ord = &o_bs.shell_order;
1264                        match (s_shl_ord, o_shl_ord) {
1265                            (ShellOrder::Pure(s_po), ShellOrder::Pure(o_po)) => Ok(
1266                                s_po.get_perm_of(&o_po)
1267                                    .unwrap()
1268                                    .image()
1269                                    .iter()
1270                                    .map(|x| s_start + x)
1271                                    .collect_vec(),
1272                            ),
1273                            (ShellOrder::Cart(s_co), ShellOrder::Cart(o_co)) => Ok(
1274                                s_co.get_perm_of(&o_co)
1275                                    .unwrap()
1276                                    .image()
1277                                    .iter()
1278                                    .map(|x| s_start + x)
1279                                    .collect_vec(),
1280                            ),
1281                            _ => Err(format_err!("At least one pair of corresponding shells have mismatched pure/cart.")),
1282                        }
1283                    } else {
1284                        Err(format_err!("At least one pair of corresponding shells have mismatched boundary indices."))
1285                    }
1286                })
1287                .collect::<Result<Vec<_>, _>>()
1288                .and_then(|image_by_shells| {
1289                    let flattened_image = image_by_shells.into_iter().flatten().collect_vec();
1290                    Permutation::from_image(flattened_image)
1291                });
1292                image
1293            } else {
1294                Err(format_err!("Mismatched numbers of shells."))
1295            }
1296        } else {
1297            Err(format_err!("Mismatched numbers of basis functions."))
1298        }
1299    }
1300}
1301
1302impl<'a> PermutableCollection for BasisAngularOrder<'a> {
1303    type Rank = usize;
1304
1305    /// Determines the permutation of [`BasisAtom`]s to map `self` to `other`.
1306    ///
1307    /// # Arguments
1308    ///
1309    /// * `other` - Another [`BasisAngularOrder`] to be compared with `self`.
1310    ///
1311    /// # Returns
1312    ///
1313    /// Returns a permutation that permutes the *ordinary* atoms of `self` to give `other`, or
1314    /// `None` if no such permutation exists.
1315    fn get_perm_of(&self, other: &Self) -> Option<Permutation<Self::Rank>> {
1316        let o_basis_atoms: HashMap<&BasisAtom, usize> = other
1317            .basis_atoms
1318            .iter()
1319            .enumerate()
1320            .map(|(i, basis_atom)| (basis_atom, i))
1321            .collect();
1322        let image_opt: Option<Vec<Self::Rank>> = self
1323            .basis_atoms
1324            .iter()
1325            .map(|s_basis_atom| o_basis_atoms.get(s_basis_atom).copied())
1326            .collect();
1327        image_opt.and_then(|image| Permutation::from_image(image).ok())
1328    }
1329
1330    fn permute(&self, perm: &Permutation<Self::Rank>) -> Result<Self, anyhow::Error> {
1331        let mut p_bao = self.clone();
1332        p_bao.permute_mut(perm)?;
1333        Ok(p_bao)
1334    }
1335
1336    fn permute_mut(&mut self, perm: &Permutation<Self::Rank>) -> Result<(), anyhow::Error> {
1337        permute_inplace(&mut self.basis_atoms, perm);
1338        Ok(())
1339    }
1340}
1341
1342impl<'a> fmt::Display for BasisAngularOrder<'a> {
1343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1344        let order_length = self
1345            .basis_shells()
1346            .map(|v| v.shell_order.to_string().chars().count())
1347            .max()
1348            .unwrap_or(20);
1349        let atom_index_length = self.n_atoms().to_string().chars().count();
1350        writeln!(f, "{}", "┈".repeat(17 + atom_index_length + order_length))?;
1351        writeln!(f, " {:>atom_index_length$}  Atom  Shell  Order", "#")?;
1352        writeln!(f, "{}", "┈".repeat(17 + atom_index_length + order_length))?;
1353        for (atm_i, batm) in self.basis_atoms.iter().enumerate() {
1354            let atm = batm.atom;
1355            for (shl_i, bshl) in batm.basis_shells.iter().enumerate() {
1356                if shl_i == 0 {
1357                    writeln!(
1358                        f,
1359                        " {:>atom_index_length$}  {:<4}  {:<5}  {:<order_length$}",
1360                        atm_i,
1361                        atm.atomic_symbol,
1362                        match &bshl.shell_order {
1363                            ShellOrder::Pure(_) | ShellOrder::Cart(_) => ANGMOM_LABELS
1364                                .get(usize::try_from(bshl.l).unwrap_or_else(|err| panic!("{err}")))
1365                                .copied()
1366                                .unwrap_or(&bshl.l.to_string())
1367                                .to_string(),
1368                            ShellOrder::Spinor(spinororder) => format!("{}/2", spinororder.two_j),
1369                        },
1370                        bshl.shell_order
1371                    )?;
1372                } else {
1373                    writeln!(
1374                        f,
1375                        " {:>atom_index_length$}  {:<4}  {:<5}  {:<order_length$}",
1376                        "",
1377                        "",
1378                        match &bshl.shell_order {
1379                            ShellOrder::Pure(_) | ShellOrder::Cart(_) => ANGMOM_LABELS
1380                                .get(usize::try_from(bshl.l).unwrap_or_else(|err| panic!("{err}")))
1381                                .copied()
1382                                .unwrap_or(&bshl.l.to_string())
1383                                .to_string(),
1384                            ShellOrder::Spinor(spinororder) => format!("{}/2", spinororder.two_j),
1385                        },
1386                        bshl.shell_order
1387                    )?;
1388                }
1389            }
1390        }
1391        writeln!(f, "{}", "┈".repeat(17 + atom_index_length + order_length))?;
1392        Ok(())
1393    }
1394}
1395
1396impl<'a> PartialEq for BasisAngularOrder<'a> {
1397    fn eq(&self, other: &Self) -> bool {
1398        self.basis_atoms == other.basis_atoms
1399    }
1400}
1401
1402impl<'a> Eq for BasisAngularOrder<'a> {}
1403
1404impl<'a> Hash for BasisAngularOrder<'a> {
1405    fn hash<H: Hasher>(&self, state: &mut H) {
1406        self.basis_atoms.hash(state);
1407    }
1408}
1409
1410impl<'a> fmt::Debug for BasisAngularOrder<'a> {
1411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1412        writeln!(f, "{:?}", self.basis_atoms)
1413    }
1414}