qsym2/interfaces/input/
ao_basis.rs

1//! Human-readable specification of atomic-orbital basis information in QSym² input configuration.
2
3use anyhow::{self, ensure, format_err};
4use derive_builder::Builder;
5use serde::{Deserialize, Serialize};
6
7use crate::auxiliary::molecule::Molecule;
8use crate::basis::ao::*;
9
10// ---------------
11// InputShellOrder
12// ---------------
13
14/// Serialisable/deserialisable enumerated type to indicate the type of the angular functions in
15/// a shell and how they are ordered.
16#[derive(Clone, Debug, Serialize, Deserialize)]
17pub enum InputShellOrder {
18    /// This variant indicates that the angular functions are real solid harmonics arranged in
19    /// increasing $`m`$ order.
20    PureIncreasingm,
21
22    /// This variant indicates that the angular functions are real solid harmonics arranged in
23    /// decreasing $`m`$ order.
24    PureDecreasingm,
25
26    /// This variant indicates that the angular functions are real solid harmonics arranged in
27    /// a custom order specified by the $`m_l`$ values.
28    PureCustom(Vec<i32>),
29
30    /// This variant indicates that the angular functions are Cartesian functions arranged in
31    /// lexicographic order.
32    CartLexicographic,
33
34    /// This variant indicates that the angular functions are Cartesian functions arranged in
35    /// Q-Chem order.
36    CartQChem,
37
38    /// This variant indicates that the angular functions are Cartesian functions arranged in
39    /// a custom order specified by the ordered exponent tuples.
40    CartCustom(Vec<(u32, u32, u32)>),
41
42    /// This variant indicates that the angular functions are spinors describing a fermion arranged
43    /// in increasing $`m`$ order. The associated boolean indicates whether the spinors are even
44    /// with respect to spatial inversion.
45    SpinorFermionIncreasingm(bool, Option<SpinorBalanceSymmetry>),
46
47    /// This variant indicates that the angular functions are spinors describing a fermion arranged
48    /// in decreasing $`m`$ order. The associated boolean indicates whether the spinors are even
49    /// with respect to spatial inversion.
50    SpinorFermionDecreasingm(bool, Option<SpinorBalanceSymmetry>),
51
52    /// This variant indicates that the angular functions are spinors describing a fermion arranged
53    /// in a custom order specified by the $`m_l`$ values.
54    SpinorFermionCustom(bool, Vec<i32>, Option<SpinorBalanceSymmetry>),
55
56    /// This variant indicates that the angular functions are spinors describing an antifermion
57    /// arranged in increasing $`m`$ order. The associated boolean indicates whether the spinors are
58    /// even with respect to spatial inversion.
59    SpinorAntifermionIncreasingm(bool, Option<SpinorBalanceSymmetry>),
60
61    /// This variant indicates that the angular functions are spinors describing an antifermion
62    /// arranged in decreasing $`m`$ order. The associated boolean indicates whether the spinors are
63    /// even with respect to spatial inversion.
64    SpinorAntifermionDecreasingm(bool, Option<SpinorBalanceSymmetry>),
65
66    /// This variant indicates that the angular functions are spinors describing an antifermion
67    /// arranged in a custom order specified by the $`m_l`$ values.
68    SpinorAntifermionCustom(bool, Vec<i32>, Option<SpinorBalanceSymmetry>),
69}
70
71impl InputShellOrder {
72    /// Converts the [`InputShellOrder`] to a corresponding [`ShellOrder`].
73    pub fn to_shell_order(&self, l: u32) -> ShellOrder {
74        match self {
75            InputShellOrder::PureIncreasingm => ShellOrder::Pure(PureOrder::increasingm(l)),
76            InputShellOrder::PureDecreasingm => ShellOrder::Pure(PureOrder::decreasingm(l)),
77            InputShellOrder::PureCustom(mls) => {
78                ShellOrder::Pure(PureOrder::new(mls).expect("Invalid ml sequence specified."))
79            }
80            InputShellOrder::CartLexicographic => ShellOrder::Cart(CartOrder::lex(l)),
81            InputShellOrder::CartQChem => ShellOrder::Cart(CartOrder::qchem(l)),
82            InputShellOrder::CartCustom(cart_tuples) => ShellOrder::Cart(
83                CartOrder::new(cart_tuples).expect("Invalid Cartesian tuples provided."),
84            ),
85            InputShellOrder::SpinorFermionIncreasingm(even, sbs) => ShellOrder::Spinor(
86                SpinorOrder::increasingm(l, *even, SpinorParticleType::Fermion(sbs.clone())),
87            ),
88            InputShellOrder::SpinorFermionDecreasingm(even, sbs) => ShellOrder::Spinor(
89                SpinorOrder::decreasingm(l, *even, SpinorParticleType::Fermion(sbs.clone())),
90            ),
91            InputShellOrder::SpinorFermionCustom(even, mls, sbs) => ShellOrder::Spinor(
92                SpinorOrder::new(mls, *even, SpinorParticleType::Fermion(sbs.clone()))
93                    .expect("Invalid 2mj sequence specified."),
94            ),
95            InputShellOrder::SpinorAntifermionIncreasingm(even, sbs) => ShellOrder::Spinor(
96                SpinorOrder::increasingm(l, *even, SpinorParticleType::Antifermion(sbs.clone())),
97            ),
98            InputShellOrder::SpinorAntifermionDecreasingm(even, sbs) => ShellOrder::Spinor(
99                SpinorOrder::decreasingm(l, *even, SpinorParticleType::Antifermion(sbs.clone())),
100            ),
101            InputShellOrder::SpinorAntifermionCustom(even, mls, sbs) => ShellOrder::Spinor(
102                SpinorOrder::new(mls, *even, SpinorParticleType::Antifermion(sbs.clone()))
103                    .expect("Invalid 2mj sequence specified."),
104            ),
105        }
106    }
107}
108
109// ---------------
110// InputBasisShell
111// ---------------
112
113/// Serialisable/deserialisable structure representing a shell in an atomic-orbital basis set.
114#[derive(Clone, Debug, Builder, Serialize, Deserialize)]
115pub struct InputBasisShell {
116    /// A non-negative integer indicating the rank of the shell.
117    pub l: u32,
118
119    /// An enum indicating the type of the angular functions in a shell and how they are ordered.
120    pub shell_order: InputShellOrder,
121}
122
123impl InputBasisShell {
124    /// Returns a builder to construct [`InputBasisShell`].
125    pub fn builder() -> InputBasisShellBuilder {
126        InputBasisShellBuilder::default()
127    }
128
129    /// Returns the number of basis functions in this shell.
130    pub fn n_funcs(&self) -> usize {
131        let lsize = self.l as usize;
132        match self.shell_order {
133            InputShellOrder::PureIncreasingm
134            | InputShellOrder::PureDecreasingm
135            | InputShellOrder::PureCustom(_) => 2 * lsize + 1, // lsize = l
136            InputShellOrder::CartQChem
137            | InputShellOrder::CartLexicographic
138            | InputShellOrder::CartCustom(_) => ((lsize + 1) * (lsize + 2)).div_euclid(2),
139            InputShellOrder::SpinorFermionIncreasingm(..)
140            | InputShellOrder::SpinorFermionDecreasingm(..)
141            | InputShellOrder::SpinorFermionCustom(..)
142            | InputShellOrder::SpinorAntifermionIncreasingm(..)
143            | InputShellOrder::SpinorAntifermionDecreasingm(..)
144            | InputShellOrder::SpinorAntifermionCustom(..) => lsize + 1, // lsize = 2j
145        }
146    }
147
148    /// Converts the [`InputBasisShell`] to a corresponding [`BasisShell`].
149    pub fn to_basis_shell(&self) -> BasisShell {
150        BasisShell::new(self.l, self.shell_order.to_shell_order(self.l))
151    }
152}
153
154// --------------
155// InputBasisAtom
156// --------------
157
158/// Serialisable/deserialisable structure containing the ordered sequence of the shells for an
159/// atom. However, unlike [`BasisAtom`], this structure does not contain a reference to the atom it
160/// is describing, but instead it only contains an index and an owned string giving the element
161/// name of the atom. This is only for serialisation/deserialisation purposes.
162#[derive(Clone, Debug, Builder, Serialize, Deserialize)]
163pub struct InputBasisAtom {
164    /// The index and name of an atom in the basis set.
165    pub atom: (usize, String),
166
167    /// The ordered shells associated with this atom.
168    pub basis_shells: Vec<InputBasisShell>,
169}
170
171impl InputBasisAtom {
172    /// Returns a builder to construct [`InputBasisAtom`].
173    pub fn builder() -> InputBasisAtomBuilder {
174        InputBasisAtomBuilder::default()
175    }
176
177    /// Returns the number of basis functions localised on this atom.
178    pub fn n_funcs(&self) -> usize {
179        self.basis_shells.iter().map(InputBasisShell::n_funcs).sum()
180    }
181
182    /// Converts to a [`BasisAtom`] structure given a molecule.
183    ///
184    /// # Arguments
185    ///
186    /// * `mol` - A molecule to which the atom in this [`InputBasisAtom`] belongs.
187    ///
188    /// # Returns
189    ///
190    /// The corresponding [`BasisAtom`] structure.
191    ///
192    /// # Errors
193    ///
194    /// Errors if the atom index and name in this [`InputBasisAtom`] do not match the
195    /// corresponding atom in `mol`.
196    pub fn to_basis_atom<'a>(&self, mol: &'a Molecule) -> Result<BasisAtom<'a>, anyhow::Error> {
197        let (atm_i, atm_s) = &self.atom;
198        let atom = &mol
199            .atoms
200            .get(*atm_i)
201            .ok_or(format_err!("Atom index {atm_i} not found."))?;
202        ensure!(
203            atom.atomic_symbol == *atm_s,
204            "Mismatched element names: {} (expected) != {atm_s} (specified).",
205            atom.atomic_symbol
206        );
207        let bss = self
208            .basis_shells
209            .iter()
210            .map(|inp_bs| inp_bs.to_basis_shell())
211            .collect::<Vec<_>>();
212        BasisAtom::builder()
213            .atom(atom)
214            .basis_shells(&bss)
215            .build()
216            .map_err(|err| format_err!(err))
217    }
218}
219
220// ----------------------
221// InputBasisAngularOrder
222// ----------------------
223
224/// Serialisable/deserialisable structure containing the angular momentum information of an
225/// atomic-orbital basis set that is required for symmetry transformation to be performed.However,
226/// unlike [`BasisAngularOrder`], this structure does not contain references to the atoms it is
227/// describing. This is only for serialisation/deserialisation purposes.
228///
229/// The associated anonymous field is an ordered sequence of [`InputBasisAtom`] in the order the
230/// atoms are defined in the molecule.
231#[derive(Clone, Debug, Serialize, Deserialize)]
232pub struct InputBasisAngularOrder(pub Vec<InputBasisAtom>);
233
234impl InputBasisAngularOrder {
235    /// Returns the number of basis functions in this basis set.
236    pub fn n_funcs(&self) -> usize {
237        self.0.iter().map(InputBasisAtom::n_funcs).sum()
238    }
239
240    /// Converts to a [`BasisAngularOrder`] structure given a molecule.
241    ///
242    /// # Arguments
243    ///
244    /// * `mol` - A molecule to which the atoms in this [`InputBasisAngularOrder`] belong.
245    ///
246    /// # Returns
247    ///
248    /// The corresponding [`BasisAngularOrder`] structure.
249    ///
250    /// # Errors
251    ///
252    /// Errors if the atom indices and names in this [`InputBasisAngularOrder`] do not match
253    /// those in `mol`.
254    pub fn to_basis_angular_order<'a>(
255        &self,
256        mol: &'a Molecule,
257    ) -> Result<BasisAngularOrder<'a>, anyhow::Error> {
258        ensure!(
259            mol.atoms.len() == self.0.len(),
260            "Mismatched numbers of atoms: {} (expected) != {} (specified).",
261            mol.atoms.len(),
262            self.0.len()
263        );
264        let basis_atoms = self
265            .0
266            .iter()
267            .map(|batm| batm.to_basis_atom(mol))
268            .collect::<Result<Vec<BasisAtom<'a>>, _>>()?;
269        BasisAngularOrder::builder()
270            .basis_atoms(&basis_atoms)
271            .build()
272            .map_err(|err| format_err!(err))
273    }
274}
275
276impl Default for InputBasisAngularOrder {
277    fn default() -> Self {
278        Self(vec![
279            InputBasisAtom::builder()
280                .atom((0, "H".to_string()))
281                .basis_shells(vec![
282                    InputBasisShell::builder()
283                        .l(0)
284                        .shell_order(InputShellOrder::PureIncreasingm)
285                        .build()
286                        .expect("Unable to construct a default input basis shell."),
287                    InputBasisShell::builder()
288                        .l(1)
289                        .shell_order(InputShellOrder::PureDecreasingm)
290                        .build()
291                        .expect("Unable to construct a default input basis shell."),
292                    InputBasisShell::builder()
293                        .l(2)
294                        .shell_order(InputShellOrder::PureCustom(vec![0, 1, -1, 2, -2]))
295                        .build()
296                        .expect("Unable to construct a default input basis shell."),
297                ])
298                .build()
299                .expect("Unable to construct a default input basis atom."),
300            InputBasisAtom::builder()
301                .atom((1, "O".to_string()))
302                .basis_shells(vec![
303                    InputBasisShell::builder()
304                        .l(1)
305                        .shell_order(InputShellOrder::CartCustom(vec![
306                            (0, 1, 0),
307                            (1, 0, 0),
308                            (0, 0, 1),
309                        ]))
310                        .build()
311                        .expect("Unable to construct a default input basis shell."),
312                    InputBasisShell::builder()
313                        .l(2)
314                        .shell_order(InputShellOrder::CartQChem)
315                        .build()
316                        .expect("Unable to construct a default input basis shell."),
317                    InputBasisShell::builder()
318                        .l(3)
319                        .shell_order(InputShellOrder::CartLexicographic)
320                        .build()
321                        .expect("Unable to construct a default input basis shell."),
322                ])
323                .build()
324                .expect("Unable to construct a default input basis atom."),
325        ])
326    }
327}