qsym2/integrals/
shell_tuple.rs

1//! Management of shell tuples for generic $`n`$-centre integrals.
2
3use std::marker::PhantomData;
4
5use derive_builder::Builder;
6use indexmap::{IndexMap, IndexSet};
7use itertools::{izip, Itertools};
8use nalgebra::{Point3, Vector3};
9use ndarray::{Array, Array1, Array2, Dim};
10use rayon::prelude::*;
11
12use crate::basis::ao_integrals::{BasisSet, BasisShellContraction};
13
14/// Structure to handle pre-computed properties of a tuple of shells consisting of
15/// non-integration primitives.
16#[derive(Builder)]
17pub(crate) struct ShellTuple<'a, const RANK: usize, T: Clone> {
18    /// The data type of the overlap values from this shell tuple.
19    typ: PhantomData<T>,
20
21    /// The non-integration shells in this shell tuple. Each shell has an associated
22    /// boolean indicating if it is to be complex-conjugated in the integral
23    /// evalulation.
24    shells: [(&'a BasisShellContraction<f64, f64>, bool); RANK],
25
26    /// A fixed-size array indicating the angular-shape of this shell tuple.
27    ///
28    /// Each element in the array gives the number of angular functions of the corresponding shell.
29    angular_shell_shape: [usize; RANK],
30
31    /// A fixed-size array indicating the primitive-shape of this shell tuple.
32    ///
33    /// Each element in the array gives the number of Gaussian primitives of the
34    /// corresponding shell.
35    primitive_shell_shape: [usize; RANK],
36
37    // -----------------------------------------------
38    // Quantities common to all primitive combinations
39    // -----------------------------------------------
40    /// A fixed-size array containing the $`\mathbf{k}`$ vectors of the shells in this
41    /// shell tuple. Each $`\mathbf{k}`$ vector is appropriately signed to take into
42    /// account the complex conjugation pattern of the shell tuple.
43    ks: [Option<Vector3<f64>>; RANK],
44
45    /// The sum of all signed $`\mathbf{k}`$ vectors across all shells.
46    k: Vector3<f64>,
47
48    /// A fixed-size array containing the Cartesian origins of the shells in this shell
49    /// tuple.
50    rs: [&'a Point3<f64>; RANK],
51
52    /// A fixed-size array containing the angular momentum orders of the shells in this
53    /// shell tuple.
54    ns: [usize; RANK],
55
56    /// A fixed-size array containing conversion matrices to convert overlap values involving
57    /// shells that have specified pure orders from internally computed Cartesian orders to the
58    /// specified pure ones.
59    rl2carts: [Option<Array2<f64>>; RANK],
60
61    // -----------------------------------------------
62    // Quantities unique to each primitive combination
63    // -----------------------------------------------
64    /// A fixed-size array of arrays of non-integration primitive exponents.
65    ///
66    /// This quantity is $`\zeta_g^{(k)}`$ appearing in Equations 81 and 83 of Honda, M.,
67    /// Sato, K. & Obara, S. Formulation of molecular integrals over Gaussian functions
68    /// treatable by both the Laplace and Fourier transforms of spatial operators by
69    /// using derivative of Fourier-kernel multiplied Gaussians. *The Journal of
70    /// Chemical Physics* **94**, 3790–3804 (1991),
71    /// [DOI](https://doi.org/10.1063/1.459751).
72    ///
73    /// The i-th array in the vector is for the i-th shell. The j-th element in that
74    /// array then gives the exponent of the j-th primitive exponent of that shell.
75    zs: [Array1<&'a f64>; RANK],
76
77    /// An array containing the sums of all possible combinations of non-integration
78    /// primitive exponents across all shells.
79    ///
80    /// This quantity is $`\zeta_G^{(k)}`$ defined in Equation 81 of Honda, M.,
81    /// Sato, K. & Obara, S. Formulation of molecular integrals over Gaussian functions
82    /// treatable by both the Laplace and Fourier transforms of spatial operators by
83    /// using derivative of Fourier-kernel multiplied Gaussians. *The Journal of
84    /// Chemical Physics* **94**, 3790–3804 (1991),
85    /// [DOI](https://doi.org/10.1063/1.459751).
86    ///
87    /// This is a `RANK`-dimensional array. The element `zg[i, j, k, ...]`
88    /// gives the sum of the i-th primitive exponent on the first shell, the j-th
89    /// primitive exponent on the second shell, the k-th primitive exponent on the
90    /// third shell, and so on.
91    zg: Array<f64, Dim<[usize; RANK]>>,
92
93    /// An array containing the products of all possible combinations of
94    /// non-integration primitive exponents across all shells.
95    ///
96    /// This is a `RANK`-dimensional array. The element `sd[i, j, k, ...]`
97    /// gives the product of the i-th primitive exponent on the first shell, the j-th
98    /// primitive exponent on the second shell, the k-th primitive exponent on the
99    /// third shell, and so on.
100    zd: Array<f64, Dim<[usize; RANK]>>,
101
102    /// A fixed-size array of arrays of contraction coefficients of non-integration
103    /// primitives.
104    ///
105    /// The i-th array in the vector is for the i-th shell. The j-th element in that
106    /// array then gives the contraction coefficient of the j-th primitive exponent of
107    /// that shell.
108    ds: [Array1<&'a f64>; RANK],
109
110    /// An array containing the product of all possible combinations of non-integration
111    /// primitive coefficients across all shells.
112    ///
113    /// This is a `RANK`-dimensional array. The element `dd[i, j, k, ...]` gives
114    /// the product of the i-th primitive's coefficient on the first shell, the j-th
115    /// primitive's coefficient on the second shell, the k-th primitive's coefficient
116    /// on the third shell, and so on.
117    dd: Array<f64, Dim<[usize; RANK]>>,
118
119    /// An array containing the exponent-weighted Cartesian centres of all possible
120    /// combinations of primitives across all shells.
121    ///
122    /// This is a `RANK`-dimensional array. The element `rg[i, j, k, ...]` gives
123    /// the exponent-weighted Cartesian centre of the i-th primitive on the first shell,
124    /// the j-th primitive on the second shell, the k-th primitive on the third shell,
125    /// and so on.
126    rg: Array<Point3<f64>, Dim<[usize; RANK]>>,
127
128    /// A fixed-size array of arrays giving the optional quantity $`\mathbf{Q}_j`$ for
129    /// the j-th shell.
130    ///
131    /// This quantity is defined in Equation 122 of Honda, M., Sato, K. & Obara, S.
132    /// Formulation of molecular integrals over Gaussian functions treatable by both
133    /// the Laplace and Fourier transforms of spatial operators by using derivative of
134    /// Fourier-kernel multiplied Gaussians. *The Journal of Chemical Physics* **94**,
135    /// 3790–3804 (1991), [DOI](https://doi.org/10.1063/1.459751). Since there are no
136    /// integration exponents in the current implementation of [`ShellTuple`], the
137    /// summation over $`v`$ in Equation 122 is not included. See also Equation 171 in
138    /// the reference.
139    ///
140    /// The j-th array is for the j-th shell. Each array is a `RANK`-dimensional
141    /// array. The element `qs[j][i, m, k, ...]` gives the $`\mathbf{Q}_j`$ vector
142    /// defined using the i-th primitive exponent on the first shell, the m-th
143    /// primitive exponent on the second shell, the k-th primitive exponent on the
144    /// third shell, and so on. The exponent-combination dependence comes from the
145    /// $`\mathbf{R}_G`$ term.
146    ///
147    /// If the j-th shell does not have a $`\mathbf{k}_j`$ plane-wave vector, then the
148    /// corresponding $`\mathbf{Q}_j`$ is set to `None`.
149    qs: [Option<Array<Vector3<f64>, Dim<[usize; RANK]>>>; RANK],
150}
151
152impl<'a, const RANK: usize, T: Clone> ShellTuple<'a, RANK, T> {
153    pub(crate) fn builder() -> ShellTupleBuilder<'a, RANK, T> {
154        ShellTupleBuilder::<RANK, T>::default()
155    }
156
157    /// The number of shells in this tuple.
158    fn rank(&self) -> usize {
159        RANK
160    }
161
162    /// The maximum angular momentum across all shells.
163    fn lmax(&self) -> u32 {
164        self.shells
165            .iter()
166            .map(|(bsc, _)| bsc.basis_shell().l)
167            .max()
168            .expect("The maximum angular momentum across all shells cannot be found.")
169    }
170}
171
172/// Structure to handle all possible shell tuples for a particular type of integral.
173#[derive(Builder)]
174pub(crate) struct ShellTupleCollection<'a, const RANK: usize, T: Clone> {
175    /// The data type of the overlap values from shell tuples in this collection.
176    typ: PhantomData<T>,
177
178    /// A fixed-size array containing basis sets, where each basis set at a certain shell position
179    /// contains all shells to be considered for that position.
180    basis_sets: [&'a BasisSet<f64, f64>; RANK],
181
182    /// The maximum angular momentum across all shells in this collection.
183    lmax: u32,
184
185    /// The complex-conjugation pattern across all shell positions in this collection.
186    ccs: [bool; RANK],
187
188    /// The numbers of shells across all shell positions in this collection.
189    n_shells: [usize; RANK],
190
191    /// The total numbers of angular functions across all shell positions in this collection. Each
192    /// value in the fixed-size array gives the total number of angular functions from all shells
193    /// at that position. In other words, this is the number of basis functions in the basis set at
194    /// that position.
195    angular_all_shell_shape: [usize; RANK],
196}
197
198impl<'a, const RANK: usize, T: Clone> ShellTupleCollection<'a, RANK, T> {
199    pub(crate) fn builder() -> ShellTupleCollectionBuilder<'a, RANK, T> {
200        ShellTupleCollectionBuilder::<RANK, T>::default()
201    }
202
203    /// The maximum angular momentum across all shell tuples.
204    fn lmax(&self) -> u32 {
205        self.lmax
206    }
207
208    /// The number of shells in each tuple in the collection.
209    fn rank(&self) -> usize {
210        RANK
211    }
212
213    /// Returns an iterator of the shell tuples unique with respect to permutations of the
214    /// constituent shells, taking into account complex conjugation, symmetry
215    /// transformation, and derivative patterns.
216    ///
217    /// # Arguments
218    ///
219    /// * `ls` - The derivative pattern.
220    fn unique_shell_tuples_iter<'it>(
221        &'it self,
222        ls: [usize; RANK],
223    ) -> UniqueShellTupleIterator<'it, 'a, RANK, T>
224    where
225        'a: 'it,
226    {
227        // Example:
228        //     ccs = [true, true, false, true, false]
229        //     ls  = [   1,    1,     0,    2,     0]
230        //     sym = [   1,    0,     0,    0,     0] -- not considered for now
231        //     nsh = [   2,    1,     2,    2,     2]
232        //           i.e. Each shell position has three possible shells.
233
234        // Each shell type is a tuple of its complex-conjugationness, its derivative
235        // order, and its length. The vector `shell_types` collects these tuples together.
236        // Example:
237        // shell_types = [
238        //     (true, 1, 3), (true, 1, 4), (false, 0, 3), (true, 2, 3), (false, 0, 3)
239        // ].
240        // We see that there are four types here, and only shell positions that
241        // have the same type have permutation equivalence, i.e. [0], [1], [2, 4], [3].
242        let shell_types: Vec<(bool, usize, usize)> =
243            izip!(self.ccs, ls, self.n_shells).collect::<Vec<_>>();
244
245        // The map `shell_types_classified` keeps track of the unique shell types in this
246        // shell tuple and the associated shell positions as tuples.
247        // Example:
248        // shell_types_classified = {
249        //     (true , 1, 2): {0},
250        //     (true , 1, 1): {1},
251        //     (false, 0, 2): {2, 4},
252        //     (true , 2, 2): {3}
253        // }.
254        let mut shell_types_classified: IndexMap<(bool, usize, usize), IndexSet<usize>> =
255            IndexMap::new();
256        shell_types
257            .into_iter()
258            .enumerate()
259            .for_each(|(shell_index, shell_type)| {
260                shell_types_classified
261                    .entry(shell_type)
262                    .or_default()
263                    .insert(shell_index);
264            });
265
266        // The map `shell_indices_unique_combinations` maps, for each shell type, the
267        // corresponding indices of shells of that type to the unique index combinations.
268        // Example:
269        // shell_type_unique_combinations = {
270        //     [0]   : [[0], [1]],
271        //     [1]   : [[0]],
272        //     [2, 4]: [[0, 0], [0, 1], [1, 1]],
273        //     [3]   : [[0], [1]],
274        // }
275        let shell_indices_unique_combinations = shell_types_classified
276            .iter()
277            .map(|(shell_type, shell_indices)| {
278                (
279                    shell_indices.iter().collect::<Vec<_>>(),
280                    (0..shell_type.2)
281                        .combinations_with_replacement(shell_indices.len())
282                        .collect::<Vec<_>>(),
283                )
284            })
285            .collect::<IndexMap<_, _>>();
286
287        // Example:
288        // sis = [0, 1, 2, 4, 3]
289        // gg = [
290        //     [[0], [1]],
291        //     [[0]],
292        //     [[0, 0], [0, 1], [1, 1]],
293        //     [[0], [1]]
294        // ]
295        // `order` gives the indices to sort `sis`.
296        // order = [0, 1, 2, 4, 3]
297        let sis = shell_indices_unique_combinations
298            .keys()
299            .flatten()
300            .collect::<Vec<_>>();
301        let mut order = (0..sis.len()).collect::<Vec<_>>();
302        order.sort_by_key(|&i| &sis[i]);
303        let gg = shell_indices_unique_combinations
304            .into_values()
305            .collect::<Vec<_>>();
306
307        // `unordered_recombined_shell_indices` forms all possible combinations of
308        // shell indices across all different shell types.
309        // Example:
310        // unordered_recombined_shell_indices = [
311        //     [[0], [0], [0, 0], [0]],
312        //     [[0], [0], [0, 0], [1]],
313        //     [[0], [0], [0, 1], [0]],
314        //     [[0], [0], [0, 1], [1]],
315        //     [[0], [0], [1, 1], [0]],
316        //     [[0], [0], [1, 1], [1]],
317        //     ...
318        // ] (12 = 2 * 1 * 3 * 2 terms in total)
319        let unordered_recombined_shell_indices = gg
320            .into_iter()
321            .multi_cartesian_product()
322            .into_iter()
323            .collect::<Vec<_>>();
324
325        log::debug!("Rank-{RANK} shell tuple collection information:");
326        log::debug!(
327            "  Total number of unique tuples: {}",
328            unordered_recombined_shell_indices.len()
329        );
330
331        UniqueShellTupleIterator::<'it, 'a, RANK, T> {
332            index: 0,
333            shell_order: order,
334            unordered_recombined_shell_indices,
335            stc: &self,
336        }
337    }
338}
339
340/// Iterator over unique shell tuples in a collection.
341struct UniqueShellTupleIterator<'it, 'a: 'it, const RANK: usize, T: Clone> {
342    /// The current index of iteration.
343    index: usize,
344
345    /// Indices to reorder shell positions in each flattened `Vec<Vec<usize>>` of
346    /// [`Self::unordered_recombined_shell_indices`] to put them in the right place.
347    shell_order: Vec<usize>,
348
349    /// All possible combinations of shell indices across all different shell types. Each
350    /// `Vec<Vec<usize>>` gives one unique shell tuple combination. Each `Vec<usize>` gives the
351    /// indices of shells within a shell type.
352    unordered_recombined_shell_indices: Vec<Vec<Vec<usize>>>,
353
354    /// The shell tuple collection with respect to which this iterator is defined.
355    stc: &'it ShellTupleCollection<'a, RANK, T>,
356}
357
358/// Implements methods for shell tuples of a specified pattern.
359macro_rules! impl_shell_tuple {
360    ( $RANK:ident, <$($shell_name:ident),+> ) => {
361        const $RANK: usize = count_exprs!($($shell_name),+);
362
363        impl<'it, 'a: 'it, T: Clone> Iterator for UniqueShellTupleIterator<'it, 'a, $RANK, T> {
364            type Item = (ShellTuple<'a, $RANK, T>, [usize; $RANK], Vec<[usize; $RANK]>);
365
366            fn next(&mut self) -> Option<Self::Item> {
367                let unordered_shell_index = self.unordered_recombined_shell_indices.get(self.index)?;
368
369                // Now, for each term in `unordered_recombined_shell_indices`, we need to
370                // flatten and then reorder to put the shell indices at the correct positions. This
371                // gives `ordered_shell_index` which gives a unique shell tuple permutation.
372                let flattened_unordered_shell_index = unordered_shell_index
373                    .clone()
374                    .into_iter()
375                    .flatten()
376                    .collect::<Vec<_>>();
377                let mut ordered_shell_index_iter = self.shell_order.iter().map(|i| {
378                    flattened_unordered_shell_index[*i]
379                });
380                $(
381                    let $shell_name = ordered_shell_index_iter
382                        .next()
383                        .expect("Shell index out of range.");
384                )+
385                let ordered_shell_index = [$($shell_name),+];
386
387                let mut ordered_shell_index_iter = ordered_shell_index.iter().enumerate();
388                $(
389                    let $shell_name = ordered_shell_index_iter
390                        .next()
391                        .map(|(shell_index, &i)| {
392                            (&self.stc.basis_sets[shell_index][i], self.stc.ccs[shell_index])
393                        })
394                        .expect("Shell index out of range.");
395                )+
396                let shell_tuple = build_shell_tuple!($($shell_name),+; T);
397
398                // For each term in `unordered_recombined_shell_indices`, all unique
399                // permutations of each sub-vector gives an equivalent permutation.
400                // Example: consider [[0], [0], [0, 1], [1]]. This gives the following
401                // equivalent permutations:
402                //   [[0], [0], [0, 1], [1]]
403                //   [[0], [0], [1, 0], [1]]
404                // There are two of them (1 * 1 * 2 * 1).
405                // Each equivalent permutation undergoes the same 'flattening' and
406                // 'reordering' process as for the unique term.
407                let equiv_perms = unordered_shell_index
408                    .iter()
409                    .map(|y| y.into_iter().permutations(y.len()).into_iter().unique())
410                    .multi_cartesian_product()
411                    .into_iter()
412                    .map(|x| {
413                        let mut equiv_perm_iter = x.into_iter().flatten().cloned();
414                        $(
415                            let $shell_name = equiv_perm_iter
416                                .next()
417                                .expect("Shell index out of range.");
418                        )+
419                        [$($shell_name),+]
420                    })
421                    .collect::<Vec<_>>();
422
423                log::debug!("Generated unique permutation: {ordered_shell_index:?}");
424                self.index += 1;
425                Some((shell_tuple, ordered_shell_index, equiv_perms))
426            }
427        }
428
429        crate::integrals::overlap::impl_shell_tuple_overlap!($RANK, <$($shell_name),+>);
430    }
431}
432
433/// Constructs a shell tuple given a pattern and a matching sequence of shells.
434///
435/// # Patterns
436///
437/// * `$shell` - A tuple `(shell, cc)` where `shell` is a [`BasisShellContraction`] and `cc` a
438/// boolean indicating if the shell is complex-conjugated.
439/// * `$ty` - The data type for the overlap values from this shell tuple.
440macro_rules! build_shell_tuple {
441    ( $($shell:expr),+; $ty:ty ) => {
442        {
443            use std::marker::PhantomData;
444
445            use itertools::Itertools;
446            use ndarray::{Array, Array1, Dim};
447            use nalgebra::{Point3, Vector3};
448
449            use crate::angmom::sh_conversion::sh_rl2cart_mat;
450            use crate::basis::ao::CartOrder;
451            use crate::integrals::shell_tuple::ShellTuple;
452
453            const RANK: usize = count_exprs!($($shell),+);
454
455            let zg = {
456                let arr_vec = [$(
457                    $shell.0.contraction.primitives.iter().map(|(e, _)| e).collect::<Vec<_>>()
458                ),+].into_iter()
459                    .multi_cartesian_product()
460                    .map(|s| s.into_iter().sum()).collect::<Vec<_>>();
461                let arr = Array::<f64, Dim<[usize; RANK]>>::from_shape_vec(
462                    ($($shell.0.contraction.primitives.len()),+), arr_vec
463                ).unwrap_or_else(|err| {
464                    log::error!("{err}");
465                    panic!("Unable to construct the {RANK}-dimensional array of exponent sums.")
466                });
467                arr
468            };
469
470            let rg = {
471                let arr_vec = [$(
472                    $shell
473                        .0
474                        .contraction.primitives
475                        .iter()
476                        .map(|(e, _)| *e * $shell.0.cart_origin).collect::<Vec<_>>()
477                ),+].into_iter()
478                    .multi_cartesian_product()
479                    .map(|s| {
480                        s.into_iter()
481                            .fold(Point3::origin(), |acc, r| acc + r.coords)
482                    })
483                    .collect::<Vec<_>>();
484                let arr = Array::<Point3<f64>, Dim<[usize; RANK]>>::from_shape_vec(
485                    ($($shell.0.contraction.primitives.len()),+), arr_vec
486                ).unwrap_or_else(|err| {
487                    log::error!("{err}");
488                    panic!("Unable to construct the {RANK}-dimensional array of exponent-weighted centres.")
489                }) / &zg;
490                arr
491            };
492
493            let qs = [$(
494                $shell.0.k().map(|_| rg.map(|r| (r - $shell.0.cart_origin().coords).coords))
495            ),+];
496
497            ShellTuple::<RANK, $ty>::builder()
498                .typ(PhantomData)
499                .shells([$($shell),+])
500                .angular_shell_shape([$($shell.0.basis_shell().n_funcs()),+])
501                .primitive_shell_shape([$($shell.0.contraction_length()),+])
502                .rs([$($shell.0.cart_origin()),+])
503                .ks([$(
504                    if $shell.1 {
505                        // true, hence -(-) = +
506                        $shell.0.k().copied()
507                    } else {
508                        // false, hence -
509                        $shell.0.k().copied().map(|k| -k)
510                    }
511                ),+])
512                .k([$(
513                    if $shell.1 {
514                        // true, hence -(-) = +
515                        $shell.0.k().copied()
516                    } else {
517                        // false, hence -
518                        $shell.0.k().copied().map(|k| -k)
519                    }
520                ),+]
521                    .into_iter()
522                    .filter_map(|k| k)
523                    .fold(Vector3::zeros(), |acc, k| acc + k))
524                .ns([$(
525                    usize::try_from($shell.0.basis_shell().l)
526                        .expect("Unable to convert an angular momentum `l` value to `usize`.")
527                ),+])
528                .rl2carts([$(
529                    match &$shell.0.basis_shell().shell_order {
530                        ShellOrder::Cart(_) | ShellOrder::Spinor(_) => None,
531                        ShellOrder::Pure(po) => Some(sh_rl2cart_mat(
532                            po.lpure,
533                            po.lpure,
534                            &CartOrder::lex(po.lpure),
535                            true,
536                            &po
537                        )),
538                    }
539                ),+])
540                .zs([$(
541                    Array1::from_iter($shell.0.contraction.primitives.iter().map(|(e, _)| e))
542                ),+])
543                .zg(zg)
544                .zd({
545                    let arr_vec = [$(
546                        $shell.0.contraction.primitives.iter().map(|(e, _)| e).collect::<Vec<_>>()
547                    ),+].into_iter()
548                        .multi_cartesian_product()
549                        .map(|s| s.into_iter().fold(1.0, |acc, e| acc * e)).collect::<Vec<_>>();
550                    let arr = Array::<f64, Dim<[usize; RANK]>>::from_shape_vec(
551                        ($($shell.0.contraction.primitives.len()),+), arr_vec
552                    ).unwrap_or_else(|err| {
553                        log::error!("{err}");
554                        panic!("Unable to construct the {RANK}-dimensional array of exponent products.")
555                    });
556                    arr
557                })
558                .ds([$(
559                    Array1::from_iter($shell.0.contraction.primitives.iter().map(|(_, c)| c))
560                ),+])
561                .dd({
562                    let arr_vec = [$(
563                        $shell.0.contraction.primitives.iter().map(|(_, c)| c).collect::<Vec<_>>()
564                    ),+].into_iter()
565                        .multi_cartesian_product()
566                        .map(|s| s.into_iter().fold(1.0, |acc, c| acc * c)).collect::<Vec<_>>();
567                    let arr = Array::<f64, Dim<[usize; RANK]>>::from_shape_vec(
568                        ($($shell.0.contraction.primitives.len()),+), arr_vec
569                    ).unwrap_or_else(|err| {
570                        log::error!("{err}");
571                        panic!("Unable to construct the {RANK}-dimensional array of coefficient products.")
572                    });
573                    arr
574                })
575                .rg(rg)
576                .qs(qs)
577                .build()
578                .unwrap_or_else(|_| panic!("Unable to construct a shell tuple of rank {RANK}."))
579        }
580    }
581}
582
583/// Constructs a shell tuple collection given a pattern and a matching sequence of basis sets.
584///
585/// # Patterns
586///
587/// * `$shell_name` - An identifier for a shell position.
588/// * `$shell_cc` - A boolean indicating if the shell position is complex-conjugated.
589/// * `$basisset` - A [`BasisSet`] giving all shells at the corresponding shell position.
590/// * `$ty` - The data type for the overlap values from this shell tuple collection.
591macro_rules! build_shell_tuple_collection {
592    ( <$($shell_name:ident),+>; $($shell_cc:expr),+; $($basisset:expr),+; $ty:ty ) => {
593        {
594            use std::marker::PhantomData;
595
596            use crate::integrals::shell_tuple::ShellTupleCollection;
597
598            const RANK: usize = count_exprs!($($shell_name),+);
599
600            let lmax: u32 = *[$(
601                $basisset.all_shells().map(|shell| shell.basis_shell.l).collect::<Vec<_>>()
602            ),+].iter()
603                .flatten()
604                .max()
605                .expect("Unable to determine the maximum angular momentum across all shells.");
606
607            let n_shells = [$($basisset.n_shells()),+];
608            log::debug!("Rank-{RANK} shell tuple collection construction:");
609            log::debug!(
610                "  Total number of tuples: {}",
611                n_shells.iter().product::<usize>()
612            );
613            ShellTupleCollection::<RANK, $ty>::builder()
614                .typ(PhantomData)
615                .basis_sets([$($basisset),+])
616                .lmax(lmax)
617                .ccs([$($shell_cc),+])
618                .n_shells(n_shells)
619                .angular_all_shell_shape([$(
620                    $basisset
621                        .all_shells()
622                        .map(|shell| shell.basis_shell().n_funcs())
623                        .sum::<usize>()
624                ),+])
625                .build()
626                .unwrap_or_else(|_| panic!("Unable to construct a shell tuple collection of rank {RANK}."))
627        }
628    }
629}
630
631use duplicate::duplicate_item;
632use factorial::DoubleFactorial;
633use log;
634use ndarray::{s, Zip};
635use ndarray_einsum_beta::*;
636use num_complex::Complex;
637use num_traits::ToPrimitive;
638
639use crate::basis::ao::{CartOrder, ShellOrder};
640
641type C128 = Complex<f64>;
642
643impl_shell_tuple![RANK_2, <s1, s2>];
644impl_shell_tuple![RANK_3, <s1, s2, s3>];
645impl_shell_tuple![RANK_4, <s1, s2, s3, s4>];
646impl_shell_tuple![RANK_5, <s1, s2, s3, s4, s5>];
647
648pub(crate) use {build_shell_tuple, build_shell_tuple_collection};
649
650#[cfg(test)]
651#[path = "shell_tuple_tests.rs"]
652mod shell_tuple_tests;
653
654#[cfg(test)]
655#[path = "shell_tuple_collection_tests.rs"]
656mod shell_tuple_collection_tests;