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;