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