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::{Itertools, izip};
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 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 #[allow(clippy::type_complexity)]
150 qs: [Option<Array<Vector3<f64>, Dim<[usize; RANK]>>>; RANK],
151}
152
153impl<'a, const RANK: usize, T: Clone> ShellTuple<'a, RANK, T> {
154 pub fn builder() -> ShellTupleBuilder<'a, RANK, T> {
155 ShellTupleBuilder::<RANK, T>::default()
156 }
157
158 /// The number of shells in this tuple.
159 pub fn rank(&self) -> usize {
160 RANK
161 }
162
163 /// A fixed-size array indicating the angular-shape of this shell tuple.
164 ///
165 /// Each element in the array gives the number of angular functions of the corresponding shell.
166 pub fn angular_shell_shape(&self) -> &[usize; RANK] {
167 &self.angular_shell_shape
168 }
169
170 /// A fixed-size array of arrays of contraction coefficients of non-integration
171 /// primitives.
172 ///
173 /// The i-th array in the vector is for the i-th shell. The j-th element in that
174 /// array then gives the contraction coefficient of the j-th primitive exponent of
175 /// that shell.
176 pub fn ds(&self) -> &[Array1<&'a f64>; RANK] {
177 &self.ds
178 }
179
180 /// The maximum angular momentum across all shells.
181 pub fn lmax(&self) -> u32 {
182 self.shells
183 .iter()
184 .map(|(bsc, _)| bsc.basis_shell().l)
185 .max()
186 .expect("The maximum angular momentum across all shells cannot be found.")
187 }
188}
189
190/// Structure to handle all possible shell tuples for a particular type of integral.
191#[derive(Builder)]
192pub struct ShellTupleCollection<'a, const RANK: usize, T: Clone> {
193 /// The data type of the overlap values from shell tuples in this collection.
194 typ: PhantomData<T>,
195
196 /// A fixed-size array containing basis sets, where each basis set at a certain shell position
197 /// contains all shells to be considered for that position.
198 basis_sets: [&'a BasisSet<f64, f64>; RANK],
199
200 /// The maximum angular momentum across all shells in this collection.
201 lmax: u32,
202
203 /// The complex-conjugation pattern across all shell positions in this collection.
204 ccs: [bool; RANK],
205
206 /// The numbers of shells across all shell positions in this collection.
207 n_shells: [usize; RANK],
208
209 /// The total numbers of angular functions across all shell positions in this collection. Each
210 /// value in the fixed-size array gives the total number of angular functions from all shells
211 /// at that position. In other words, this is the number of basis functions in the basis set at
212 /// that position.
213 angular_all_shell_shape: [usize; RANK],
214}
215
216impl<'a, const RANK: usize, T: Clone> ShellTupleCollection<'a, RANK, T> {
217 pub fn builder() -> ShellTupleCollectionBuilder<'a, RANK, T> {
218 ShellTupleCollectionBuilder::<RANK, T>::default()
219 }
220
221 /// The maximum angular momentum across all shell tuples.
222 pub fn lmax(&self) -> u32 {
223 self.lmax
224 }
225
226 /// The number of shells in each tuple in the collection.
227 pub fn rank(&self) -> usize {
228 RANK
229 }
230
231 /// Returns an iterator of the shell tuples unique with respect to permutations of the
232 /// constituent shells, taking into account complex conjugation, symmetry
233 /// transformation, and derivative patterns.
234 ///
235 /// # Arguments
236 ///
237 /// * `ls` - The derivative pattern.
238 fn unique_shell_tuples_iter<'it>(
239 &'it self,
240 ls: [usize; RANK],
241 ) -> UniqueShellTupleIterator<'it, 'a, RANK, T>
242 where
243 'a: 'it,
244 {
245 // Example:
246 // ccs = [true, true, false, true, false]
247 // ls = [ 1, 1, 0, 2, 0]
248 // sym = [ 1, 0, 0, 0, 0] -- not considered for now
249 // nsh = [ 2, 1, 2, 2, 2]
250 // i.e. Each shell position has three possible shells.
251
252 // Each shell type is a tuple of its complex-conjugationness, its derivative
253 // order, and its length. The vector `shell_types` collects these tuples together.
254 // Example:
255 // shell_types = [
256 // (true, 1, 3), (true, 1, 4), (false, 0, 3), (true, 2, 3), (false, 0, 3)
257 // ].
258 // We see that there are four types here, and only shell positions that
259 // have the same type have permutation equivalence, i.e. [0], [1], [2, 4], [3].
260 let shell_types: Vec<(bool, usize, usize)> =
261 izip!(self.ccs, ls, self.n_shells).collect::<Vec<_>>();
262
263 // The map `shell_types_classified` keeps track of the unique shell types in this
264 // shell tuple and the associated shell positions as tuples.
265 // Example:
266 // shell_types_classified = {
267 // (true , 1, 2): {0},
268 // (true , 1, 1): {1},
269 // (false, 0, 2): {2, 4},
270 // (true , 2, 2): {3}
271 // }.
272 let mut shell_types_classified: IndexMap<(bool, usize, usize), IndexSet<usize>> =
273 IndexMap::new();
274 shell_types
275 .into_iter()
276 .enumerate()
277 .for_each(|(shell_index, shell_type)| {
278 shell_types_classified
279 .entry(shell_type)
280 .or_default()
281 .insert(shell_index);
282 });
283
284 // The map `shell_indices_unique_combinations` maps, for each shell type, the
285 // corresponding indices of shells of that type to the unique index combinations.
286 // Example:
287 // shell_type_unique_combinations = {
288 // [0] : [[0], [1]],
289 // [1] : [[0]],
290 // [2, 4]: [[0, 0], [0, 1], [1, 1]],
291 // [3] : [[0], [1]],
292 // }
293 let shell_indices_unique_combinations = shell_types_classified
294 .iter()
295 .map(|(shell_type, shell_indices)| {
296 (
297 shell_indices.iter().collect::<Vec<_>>(),
298 (0..shell_type.2)
299 .combinations_with_replacement(shell_indices.len())
300 .collect::<Vec<_>>(),
301 )
302 })
303 .collect::<IndexMap<_, _>>();
304
305 // Example:
306 // sis = [0, 1, 2, 4, 3]
307 // gg = [
308 // [[0], [1]],
309 // [[0]],
310 // [[0, 0], [0, 1], [1, 1]],
311 // [[0], [1]]
312 // ]
313 // `order` gives the indices to sort `sis`.
314 // order = [0, 1, 2, 4, 3]
315 let sis = shell_indices_unique_combinations
316 .keys()
317 .flatten()
318 .collect::<Vec<_>>();
319 let mut order = (0..sis.len()).collect::<Vec<_>>();
320 order.sort_by_key(|&i| &sis[i]);
321 let gg = shell_indices_unique_combinations
322 .into_values()
323 .collect::<Vec<_>>();
324
325 // `unordered_recombined_shell_indices` forms all possible combinations of
326 // shell indices across all different shell types.
327 // Example:
328 // unordered_recombined_shell_indices = [
329 // [[0], [0], [0, 0], [0]],
330 // [[0], [0], [0, 0], [1]],
331 // [[0], [0], [0, 1], [0]],
332 // [[0], [0], [0, 1], [1]],
333 // [[0], [0], [1, 1], [0]],
334 // [[0], [0], [1, 1], [1]],
335 // ...
336 // ] (12 = 2 * 1 * 3 * 2 terms in total)
337 let unordered_recombined_shell_indices =
338 gg.into_iter().multi_cartesian_product().collect::<Vec<_>>();
339
340 log::debug!("Rank-{RANK} shell tuple collection information:");
341 log::debug!(
342 " Total number of unique tuples: {}",
343 unordered_recombined_shell_indices.len()
344 );
345
346 UniqueShellTupleIterator::<'it, 'a, RANK, T> {
347 index: 0,
348 shell_order: order,
349 unordered_recombined_shell_indices,
350 stc: self,
351 }
352 }
353}
354
355/// Iterator over unique shell tuples in a collection.
356struct UniqueShellTupleIterator<'it, 'a: 'it, const RANK: usize, T: Clone> {
357 /// The current index of iteration.
358 index: usize,
359
360 /// Indices to reorder shell positions in each flattened `Vec<Vec<usize>>` of
361 /// [`Self::unordered_recombined_shell_indices`] to put them in the right place.
362 shell_order: Vec<usize>,
363
364 /// All possible combinations of shell indices across all different shell types. Each
365 /// `Vec<Vec<usize>>` gives one unique shell tuple combination. Each `Vec<usize>` gives the
366 /// indices of shells within a shell type.
367 unordered_recombined_shell_indices: Vec<Vec<Vec<usize>>>,
368
369 /// The shell tuple collection with respect to which this iterator is defined.
370 stc: &'it ShellTupleCollection<'a, RANK, T>,
371}
372
373/// Implements methods for shell tuples of a specified pattern.
374macro_rules! impl_shell_tuple {
375 ( $RANK:ident, <$($shell_name:ident),+> ) => {
376 const $RANK: usize = count_exprs!($($shell_name),+);
377
378 impl<'it, 'a: 'it, T: Clone> Iterator for UniqueShellTupleIterator<'it, 'a, $RANK, T> {
379 type Item = (ShellTuple<'a, $RANK, T>, [usize; $RANK], Vec<[usize; $RANK]>);
380
381 fn next(&mut self) -> Option<Self::Item> {
382 let unordered_shell_index = self.unordered_recombined_shell_indices.get(self.index)?;
383
384 // Now, for each term in `unordered_recombined_shell_indices`, we need to
385 // flatten and then reorder to put the shell indices at the correct positions. This
386 // gives `ordered_shell_index` which gives a unique shell tuple permutation.
387 let flattened_unordered_shell_index = unordered_shell_index
388 .clone()
389 .into_iter()
390 .flatten()
391 .collect::<Vec<_>>();
392 let mut ordered_shell_index_iter = self.shell_order.iter().map(|i| {
393 flattened_unordered_shell_index[*i]
394 });
395 $(
396 let $shell_name = ordered_shell_index_iter
397 .next()
398 .expect("Shell index out of range.");
399 )+
400 let ordered_shell_index = [$($shell_name),+];
401
402 let mut ordered_shell_index_iter = ordered_shell_index.iter().enumerate();
403 $(
404 let $shell_name = ordered_shell_index_iter
405 .next()
406 .map(|(shell_index, &i)| {
407 (&self.stc.basis_sets[shell_index][i], self.stc.ccs[shell_index])
408 })
409 .expect("Shell index out of range.");
410 )+
411 let shell_tuple = build_shell_tuple!($($shell_name),+; T);
412
413 // For each term in `unordered_recombined_shell_indices`, all unique
414 // permutations of each sub-vector gives an equivalent permutation.
415 // Example: consider [[0], [0], [0, 1], [1]]. This gives the following
416 // equivalent permutations:
417 // [[0], [0], [0, 1], [1]]
418 // [[0], [0], [1, 0], [1]]
419 // There are two of them (1 * 1 * 2 * 1).
420 // Each equivalent permutation undergoes the same 'flattening' and
421 // 'reordering' process as for the unique term.
422 let equiv_perms = unordered_shell_index
423 .iter()
424 .map(|y| y.into_iter().permutations(y.len()).into_iter().unique())
425 .multi_cartesian_product()
426 .into_iter()
427 .map(|x| {
428 let mut equiv_perm_iter = x.into_iter().flatten().cloned();
429 $(
430 let $shell_name = equiv_perm_iter
431 .next()
432 .expect("Shell index out of range.");
433 )+
434 [$($shell_name),+]
435 })
436 .collect::<Vec<_>>();
437
438 log::debug!("Generated unique permutation: {ordered_shell_index:?}");
439 self.index += 1;
440 Some((shell_tuple, ordered_shell_index, equiv_perms))
441 }
442 }
443
444 crate::integrals::overlap::impl_shell_tuple_overlap!($RANK, <$($shell_name),+>);
445 }
446}
447
448/// Constructs a shell tuple given a pattern and a matching sequence of shells.
449///
450/// # Patterns
451///
452/// * `$shell` - A tuple `(shell, cc)` where `shell` is a [`BasisShellContraction`] and `cc` a
453/// boolean indicating if the shell is complex-conjugated.
454/// * `$ty` - The data type for the overlap values from this shell tuple.
455macro_rules! build_shell_tuple {
456 ( $($shell:expr),+; $ty:ty ) => {
457 {
458 use std::marker::PhantomData;
459
460 use itertools::Itertools;
461 use ndarray::{Array, Array1, Dim};
462 use nalgebra::{Point3, Vector3};
463
464 use crate::angmom::sh_conversion::sh_rl2cart_mat;
465 use crate::basis::ao::CartOrder;
466 use crate::integrals::shell_tuple::ShellTuple;
467
468 const RANK: usize = count_exprs!($($shell),+);
469
470 let zg = {
471 let arr_vec = [$(
472 $shell.0.contraction.primitives.iter().map(|(e, _)| e).collect::<Vec<_>>()
473 ),+].into_iter()
474 .multi_cartesian_product()
475 .map(|s| s.into_iter().sum()).collect::<Vec<_>>();
476 let arr = Array::<f64, Dim<[usize; RANK]>>::from_shape_vec(
477 ($($shell.0.contraction.primitives.len()),+), arr_vec
478 ).unwrap_or_else(|err| {
479 log::error!("{err}");
480 panic!("Unable to construct the {RANK}-dimensional array of exponent sums.")
481 });
482 arr
483 };
484
485 let rg = {
486 let arr_vec = [$(
487 $shell
488 .0
489 .contraction.primitives
490 .iter()
491 .map(|(e, _)| *e * $shell.0.cart_origin).collect::<Vec<_>>()
492 ),+].into_iter()
493 .multi_cartesian_product()
494 .map(|s| {
495 s.into_iter()
496 .fold(Point3::origin(), |acc, r| acc + r.coords)
497 })
498 .collect::<Vec<_>>();
499 let arr = Array::<Point3<f64>, Dim<[usize; RANK]>>::from_shape_vec(
500 ($($shell.0.contraction.primitives.len()),+), arr_vec
501 ).unwrap_or_else(|err| {
502 log::error!("{err}");
503 panic!("Unable to construct the {RANK}-dimensional array of exponent-weighted centres.")
504 }) / &zg;
505 arr
506 };
507
508 let qs = [$(
509 $shell.0.k().map(|_| rg.map(|r| (r - $shell.0.cart_origin().coords).coords))
510 ),+];
511
512 ShellTuple::<RANK, $ty>::builder()
513 .typ(PhantomData)
514 .shells([$($shell),+])
515 .angular_shell_shape([$($shell.0.basis_shell().n_funcs()),+])
516 .primitive_shell_shape([$($shell.0.contraction_length()),+])
517 .rs([$($shell.0.cart_origin()),+])
518 .ks([$(
519 if $shell.1 {
520 // true, hence -(-) = +
521 $shell.0.k().copied()
522 } else {
523 // false, hence -
524 $shell.0.k().copied().map(|k| -k)
525 }
526 ),+])
527 .k([$(
528 if $shell.1 {
529 // true, hence -(-) = +
530 $shell.0.k().copied()
531 } else {
532 // false, hence -
533 $shell.0.k().copied().map(|k| -k)
534 }
535 ),+]
536 .into_iter()
537 .filter_map(|k| k)
538 .fold(Vector3::zeros(), |acc, k| acc + k))
539 .ns([$(
540 usize::try_from($shell.0.basis_shell().l)
541 .expect("Unable to convert an angular momentum `l` value to `usize`.")
542 ),+])
543 .rl2carts([$(
544 match &$shell.0.basis_shell().shell_order {
545 ShellOrder::Cart(_) | ShellOrder::Spinor(_) => None,
546 ShellOrder::Pure(po) => Some(sh_rl2cart_mat(
547 po.lpure,
548 po.lpure,
549 &CartOrder::lex(po.lpure),
550 true,
551 &po
552 )),
553 }
554 ),+])
555 .zs([$(
556 Array1::from_iter($shell.0.contraction.primitives.iter().map(|(e, _)| e))
557 ),+])
558 .zg(zg)
559 .zd({
560 let arr_vec = [$(
561 $shell.0.contraction.primitives.iter().map(|(e, _)| e).collect::<Vec<_>>()
562 ),+].into_iter()
563 .multi_cartesian_product()
564 .map(|s| s.into_iter().fold(1.0, |acc, e| acc * e)).collect::<Vec<_>>();
565 let arr = Array::<f64, Dim<[usize; RANK]>>::from_shape_vec(
566 ($($shell.0.contraction.primitives.len()),+), arr_vec
567 ).unwrap_or_else(|err| {
568 log::error!("{err}");
569 panic!("Unable to construct the {RANK}-dimensional array of exponent products.")
570 });
571 arr
572 })
573 .ds([$(
574 Array1::from_iter($shell.0.contraction.primitives.iter().map(|(_, c)| c))
575 ),+])
576 .dd({
577 let arr_vec = [$(
578 $shell.0.contraction.primitives.iter().map(|(_, c)| c).collect::<Vec<_>>()
579 ),+].into_iter()
580 .multi_cartesian_product()
581 .map(|s| s.into_iter().fold(1.0, |acc, c| acc * c)).collect::<Vec<_>>();
582 let arr = Array::<f64, Dim<[usize; RANK]>>::from_shape_vec(
583 ($($shell.0.contraction.primitives.len()),+), arr_vec
584 ).unwrap_or_else(|err| {
585 log::error!("{err}");
586 panic!("Unable to construct the {RANK}-dimensional array of coefficient products.")
587 });
588 arr
589 })
590 .rg(rg)
591 .qs(qs)
592 .build()
593 .unwrap_or_else(|_| panic!("Unable to construct a shell tuple of rank {RANK}."))
594 }
595 }
596}
597
598/// Constructs a shell tuple collection given a pattern and a matching sequence of basis sets.
599///
600/// # Patterns
601///
602/// * `$shell_name` - An identifier for a shell position.
603/// * `$shell_cc` - A boolean indicating if the shell position is complex-conjugated.
604/// * `$basisset` - A [`BasisSet`] giving all shells at the corresponding shell position.
605/// * `$ty` - The data type for the overlap values from this shell tuple collection.
606macro_rules! build_shell_tuple_collection {
607 ( <$($shell_name:ident),+>; $($shell_cc:expr),+; $($basisset:expr),+; $ty:ty ) => {
608 {
609 use std::marker::PhantomData;
610
611 use crate::integrals::shell_tuple::ShellTupleCollection;
612
613 const RANK: usize = count_exprs!($($shell_name),+);
614
615 let lmax: u32 = *[$(
616 $basisset.all_shells().map(|shell| shell.basis_shell.l).collect::<Vec<_>>()
617 ),+].iter()
618 .flatten()
619 .max()
620 .expect("Unable to determine the maximum angular momentum across all shells.");
621
622 let n_shells = [$($basisset.n_shells()),+];
623 log::debug!("Rank-{RANK} shell tuple collection construction:");
624 log::debug!(
625 " Total number of tuples: {}",
626 n_shells.iter().product::<usize>()
627 );
628 ShellTupleCollection::<RANK, $ty>::builder()
629 .typ(PhantomData)
630 .basis_sets([$($basisset),+])
631 .lmax(lmax)
632 .ccs([$($shell_cc),+])
633 .n_shells(n_shells)
634 .angular_all_shell_shape([$(
635 $basisset
636 .all_shells()
637 .map(|shell| shell.basis_shell().n_funcs())
638 .sum::<usize>()
639 ),+])
640 .build()
641 .unwrap_or_else(|_| panic!("Unable to construct a shell tuple collection of rank {RANK}."))
642 }
643 }
644}
645
646use duplicate::duplicate_item;
647use factorial::DoubleFactorial;
648use log;
649use ndarray::{Zip, s};
650use ndarray_einsum::*;
651use num_complex::Complex;
652use num_traits::ToPrimitive;
653
654use crate::basis::ao::{CartOrder, ShellOrder};
655
656type C128 = Complex<f64>;
657
658impl_shell_tuple![RANK_2, <s1, s2>];
659impl_shell_tuple![RANK_3, <s1, s2, s3>];
660impl_shell_tuple![RANK_4, <s1, s2, s3, s4>];
661impl_shell_tuple![RANK_5, <s1, s2, s3, s4, s5>];
662
663pub(crate) use {build_shell_tuple, build_shell_tuple_collection};
664
665#[cfg(test)]
666#[path = "shell_tuple_tests.rs"]
667mod shell_tuple_tests;
668
669#[cfg(test)]
670#[path = "shell_tuple_collection_tests.rs"]
671mod shell_tuple_collection_tests;