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}