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}