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