qsym2/integrals/
overlap.rs

1//! Atomic-orbital $`n`$-centre overlap evaluations.
2
3/// Implements the `overlap` method for [`crate::integrals::shell_tuple::ShellTuple`] and
4/// [`crate::integrals::shell_tuple::ShellTupleCollection`] of a specified pattern.
5macro_rules! impl_shell_tuple_overlap {
6    ( $RANK:ident, <$($shell_name:ident),+> ) => {
7        #[duplicate_item(
8            [
9                dtype [ f64 ]
10                zg_type [ &self.zg ]
11                dd_type [ &self.dd ]
12                rl2cart_type [
13                    self.rl2carts[i]
14                        .as_ref()
15                        .unwrap_or_else(|| panic!("Transformation matrix to convert shell {i} to spherical order not found."))
16                ]
17                exp_kqs_func [
18                    if self.ks.iter().any(|k| k.is_some()) {
19                        panic!("Real-valued overlaps cannot handle plane-wave vectors.")
20                    } else {
21                        (
22                            None::<Vec<Array<f64, Dim<[usize; $RANK]>>>>,
23                            None::<Vec<Array<f64, Dim<[usize; $RANK]>>>>,
24                        )
25                    }
26                ]
27                exp_ks_kqs_to_int_func [
28                    (0..3).for_each(|i| {
29                        match (exp_ks_opt.as_ref(), exp_kqs_opt.as_ref()) {
30                            (Some(_), _) | (_, Some(_)) => {
31                                panic!("Real-valued overlaps cannot handle plane-wave vectors.")
32                            }
33                            (None, None) => {
34                                ints_r[i][l_tuple][n_tuple] = Some(
35                                    &pre_zg * &exp_zgs[i]
36                                );
37                            }
38                        }
39                    })
40                ]
41                n_recur_k_term_kk [ _ ]
42                n_recur_k_term_func [
43                    panic!("Real-valued overlaps cannot handle plane-wave vectors.")
44                ]
45                l_recur_k_term_kr [ _ ]
46                l_recur_k_term_func [
47                    panic!("Real-valued overlaps cannot handle plane-wave vectors.")
48                ]
49            ]
50            [
51                dtype [ C128 ]
52                zg_type [ self.zg.mapv(C128::from) ]
53                dd_type [ &self.dd.mapv(C128::from) ]
54                rl2cart_type [
55                    &self.rl2carts[i]
56                        .as_ref()
57                        .unwrap_or_else(|| panic!("Transformation matrix to convert shell {i} to spherical order not found."))
58                        .mapv(C128::from)
59                ]
60                exp_kqs_func [
61                    if self.ks.iter().any(|k| k.is_some()) {
62                        // exp_ks = exp(-|k|^2 / 4 zg)
63                        // exp_ks[i] is the contribution from the ith Cartesian component.
64                        // zg is primitive-combination-specific.
65                        let exp_ks = (0..3).map(|i| {
66                            self.zg.mapv(|zg| {
67                                (-self.k[i].abs().powi(2) / (4.0 * zg)).exp()
68                            })
69                        }).collect::<Vec<_>>();
70
71                        // exp_kqs = exp(ii * sum(j) k_j · q_j)
72                        // exp_kqs[i] is the contribution from the ith Cartesian component.
73                        // q_j is primitive-combination-specific.
74                        let exp_kqs = (0..3).map(|i| {
75                            (0..$RANK).filter_map(|j| {
76                                match (self.ks[j], self.qs[j].as_ref()) {
77                                    (Some(kj), Some(qj)) => {
78                                        Some(
79                                            qj.mapv(|qjj| kj[i] * qjj[i])
80                                        )
81                                    }
82                                    _ => None
83                                }
84                            })
85                            .fold(
86                                Array::<C128, Dim<[usize; $RANK]>>::zeros(self.zg.raw_dim()),
87                                |acc, arr| acc + arr
88                            )
89                            .mapv(|x| (x * C128::i()).exp())
90                        })
91                        .collect::<Vec<_>>();
92                        (Some(exp_ks), Some(exp_kqs))
93                    } else {
94                        (None, None)
95                    }
96                ]
97                exp_ks_kqs_to_int_func [
98                    (0..3).for_each(|i| {
99                        match (exp_ks_opt.as_ref(), exp_kqs_opt.as_ref()) {
100                            // Element-wise multiplication. Each element is for a specific
101                            // primitive combination.
102                            (Some(exp_ks), Some(exp_kqs)) => {
103                                ints_r[i][l_tuple][n_tuple] = Some(
104                                    (&pre_zg * &exp_zgs[i] * &exp_ks[i]).mapv(C128::from)
105                                        * &exp_kqs[i]
106                                );
107                            }
108                            _ => {
109                                ints_r[i][l_tuple][n_tuple] = Some(
110                                    (&pre_zg * &exp_zgs[i]).mapv(C128::from)
111                                );
112                            }
113                        }
114                    })
115                ]
116                n_recur_k_term_kk [ kk ]
117                n_recur_k_term_func [
118                    // 1 / (2 * zg) * sum(i) ii * k_iα * [[:|:]]
119                    // zg is primitive-combination-specific.
120                    (0..3).for_each(|i| {
121                        let add_term = self.zg.mapv(|zg| {
122                            C128::i() * kk[i] / (2.0 * zg)
123                        }) * ints_r[i][l_tuple][n_tuple].as_ref().unwrap_or_else(|| {
124                            panic!("({l_tuple:?}, {n_tuple:?}) => ({l_tuple:?}, {next_n_tuple:?}) failed.")
125                        });
126                        if let Some(arr) = ints_r[i][l_tuple][next_n_tuple].as_mut() {
127                            Zip::from(arr).and(&add_term).for_each(|a, &t| *a += t);
128                        } else {
129                            ints_r[i][l_tuple][next_n_tuple] = Some(add_term);
130                        }
131                    });
132                ]
133                l_recur_k_term_kr [ kr ]
134                l_recur_k_term_func [
135                    // -ii * k_gα * [[:|:]]
136                    (0..3).for_each(|i| {
137                        let add_term = C128::i()
138                        * kr[i]
139                        * ints_r[i][l_tuple][n_tuple].as_ref().unwrap_or_else(|| {
140                            panic!("({l_tuple:?}, {n_tuple:?}) => ({next_l_tuple:?}, {n_tuple:?}) failed.")
141                        });
142                        if let Some(arr) = ints_r[i][next_l_tuple][n_tuple].as_mut() {
143                            Zip::from(arr).and(&add_term).for_each(|a, &t| *a -= t);
144                        } else {
145                            ints_r[i][next_l_tuple][n_tuple] = Some(-add_term);
146                        }
147                    });
148                ]
149            ]
150        )]
151        impl<'a> ShellTuple<'a, $RANK, dtype> {
152            /// Calculates the overlap arrays for this shell tuple.
153            ///
154            /// # Arguments
155            ///
156            /// * `ls` - The derivative pattern.
157            ///
158            /// # Returns
159            ///
160            /// A vector of overlap arrays, each of which is for one derivative component.
161            pub fn overlap(
162                &self, ls: [usize; $RANK]
163            ) -> Vec<Array<dtype, Dim<[usize; $RANK]>>> {
164                // ~~~~~~~~~~~~~~~~~~~
165                // Preparation begins.
166                // ~~~~~~~~~~~~~~~~~~~
167
168                // We require extra Cartesian degrees to calculate derivatives, because each
169                // derivative order increases a corresponding Cartesian rank by one.
170                let ns: [usize; $RANK] = if ls.iter().any(|l| *l > 0) {
171                    let mut ns = self.ns.clone();
172                    ns.iter_mut().for_each(|n| *n += 1);
173                    ns
174                } else {
175                    self.ns.clone()
176                };
177
178                // Generate all terms in recurrence series
179                // First index: Cartesian component
180                // Next stc.rank indices: l-recursion indices
181                // Next stc.rank indices: n-recursion indices
182                // Last stc.rank indices: primitive indices
183                // E.g.: rank 3,
184                //   ints_r[1][(0, 0, 1)][(0, 1, 2)][(3, 8, 7)]: y-component integral value with
185                //     0th y-derivative of 0th Cartesian y-power of 3rd primitive on first shell,
186                //     0th y-derivative of 1st Cartesian y-power of 8th primitive on second shell, and
187                //     1st y-derivative of 2nd Cartesian y-power of 7th primitive on third shell
188                let lrecursion_shape = {
189                    let mut ls_mut = ls.clone();
190                    ls_mut.iter_mut().for_each(|l| *l += 1);
191                    ls_mut
192                };
193                let nrecursion_shape = {
194                    let mut ns_mut = ns.clone();
195                    ns_mut.iter_mut().for_each(|n| *n += 1);
196                    ns_mut
197                };
198                let arr = Array::<_, Dim<[usize; $RANK]>>::from_elem(
199                    lrecursion_shape, Array::<_, Dim<[usize; $RANK]>>::from_elem(
200                        nrecursion_shape, None::<Array::<dtype, Dim<[usize; $RANK]>>>
201                    )
202                );
203                let mut ints_r = [arr.clone(), arr.clone(), arr];
204
205                let default_tuple = [$(replace_expr!(($shell_name) 0)),+];
206                let l_tuples = ls
207                    .iter()
208                    .map(|l| 0..=*l)
209                    .multi_cartesian_product()
210                    .map(|ltuple| {
211                        let mut ltuple_arr = default_tuple.clone();
212                        ltuple_arr.iter_mut().enumerate().for_each(|(i, l)| *l = ltuple[i]);
213                        ltuple_arr
214                    })
215                    .collect::<Vec<_>>();
216                let n_tuples = ns
217                    .iter()
218                    .map(|n| 0..=*n)
219                    .multi_cartesian_product()
220                    .map(|ntuple| {
221                        let mut ntuple_arr = default_tuple.clone();
222                        ntuple_arr.iter_mut().enumerate().for_each(|(i, n)| *n = ntuple[i]);
223                        ntuple_arr
224                    })
225                    .collect::<Vec<_>>();
226                let n_tuples_noextra = ns
227                    .iter()
228                    .map(|n| 0..*n)
229                    .multi_cartesian_product()
230                    .map(|ntuple| {
231                        let mut ntuple_arr = default_tuple.clone();
232                        ntuple_arr.iter_mut().enumerate().for_each(|(i, n)| *n = ntuple[i]);
233                        ntuple_arr
234                    })
235                    .collect::<Vec<_>>();
236
237                let all_tuples = l_tuples.iter().cloned().cartesian_product(
238                    n_tuples.iter().cloned()
239                ).into_iter().collect::<IndexSet<_>>();
240                let mut remaining_tuples = all_tuples.clone();
241
242                let remaining_tuples_noextra = l_tuples.iter().cloned().cartesian_product(
243                    n_tuples_noextra.iter().cloned()
244                ).into_iter().collect::<IndexSet<_>>();
245
246                let extra_tuples = all_tuples
247                    .difference(&remaining_tuples_noextra)
248                    .cloned()
249                    .collect::<IndexSet<_>>();
250                // ~~~~~~~~~~~~~~~~~
251                // Preparation ends.
252                // ~~~~~~~~~~~~~~~~~
253
254                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
255                // Loop over all tuples begins.
256                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
257                for (tuple_index, (l_tuple, n_tuple)) in all_tuples.into_iter().enumerate() {
258                    // ~~~~~~~~~~~~~~~~~~~~
259                    // Initial term begins.
260                    // ~~~~~~~~~~~~~~~~~~~~
261                    if tuple_index == 0 {
262                        debug_assert!(remaining_tuples.shift_remove(&(l_tuple, n_tuple)));
263
264                        // pre_zg = sqrt(pi / zg)
265                        // zg is primitive-combination-specific.
266                        let pre_zg = self.zg.mapv(|zg| (std::f64::consts::PI / zg).sqrt());
267
268                        // exp_zgs = sum(g < h) [ -(z_g * z_h) / zg * |r_g - r_h|^2 ]
269                        // exp_zgs[i] is the contribution from the ith Cartesian component.
270                        // z_g, z_h, and zg are primitive-combination-specific.
271                        let exp_zgs = (0..3).map(|i| {
272                            let mut exp_zg_i = self.zg.clone();
273                            exp_zg_i.indexed_iter_mut().for_each(|(indices, zg)| {
274                                let ($($shell_name),+) = indices;
275                                let indices = [$($shell_name),+];
276                                *zg = (
277                                    -1.0
278                                    / *zg
279                                    * (0..$RANK).flat_map(|g| ((g + 1)..$RANK).map(move |h| {
280                                        self.zs[g][indices[g]]
281                                            * self.zs[h][indices[h]]
282                                            * (self.rs[g][i] - self.rs[h][i]).powi(2)
283                                    })).sum::<f64>()
284                                ).exp();
285                            });
286                            exp_zg_i
287                        }).collect::<Vec<_>>();
288
289                        // let (exp_ks_opt, exp_kqs_opt) = if self.ks.iter().any(|k| k.is_some()) {
290                        //     // exp_ks = exp(-|k|^2 / 4 zg)
291                        //     // exp_ks[i] is the contribution from the ith Cartesian component.
292                        //     // zg is primitive-combination-specific.
293                        //     let exp_ks = (0..3).map(|i| {
294                        //         self.zg.mapv(|zg| {
295                        //             (-self.k[i].abs().powi(2) / (4.0 * zg)).exp()
296                        //         })
297                        //     }).collect::<Vec<_>>();
298
299                        //     // exp_kqs = exp(ii * sum(j) k_j · q_j)
300                        //     // exp_kqs[i] is the contribution from the ith Cartesian component.
301                        //     // q_j is primitive-combination-specific.
302                        //     let exp_kqs = (0..3).map(|i| {
303                        //         (0..$RANK).filter_map(|j| {
304                        //             match (self.ks[j], self.qs[j].as_ref()) {
305                        //                 (Some(kj), Some(qj)) => {
306                        //                     Some(
307                        //                         qj.mapv(|qjj| kj[i] * qjj[i])
308                        //                     )
309                        //                 }
310                        //                 _ => None
311                        //             }
312                        //         })
313                        //         .fold(
314                        //             Array::<C128, Dim<[usize; $RANK]>>::zeros(self.zg.raw_dim()),
315                        //             |acc, arr| acc + arr
316                        //         )
317                        //         .mapv(|x| (x * C128::i()).exp())
318                        //     })
319                        //     .collect::<Vec<_>>();
320                        //     (Some(exp_ks), Some(exp_kqs))
321                        // } else {
322                        //     (None, None)
323                        // };
324                        let (exp_ks_opt, exp_kqs_opt) = exp_kqs_func;
325
326                        // (0..3).for_each(|i| {
327                        //     match (exp_ks_opt.as_ref(), exp_kqs_opt.as_ref()) {
328                        //         // Element-wise multiplication. Each element is for a specific
329                        //         // primitive combination.
330                        //         (Some(exp_ks), Some(exp_kqs)) => {
331                        //             ints_r[i][l_tuple][n_tuple] = Some(
332                        //                 (&pre_zg * &exp_zgs[i] * &exp_ks[i]).mapv(C128::from)
333                        //                     * &exp_kqs[i]
334                        //             );
335                        //         }
336                        //         _ => {
337                        //             ints_r[i][l_tuple][n_tuple] = Some(
338                        //                 (&pre_zg * &exp_zgs[i]).mapv(C128::from)
339                        //             );
340                        //         }
341                        //     }
342                        // });
343                        exp_ks_kqs_to_int_func
344                    }
345                    // ~~~~~~~~~~~~~~~~~~
346                    // Initial term ends.
347                    // ~~~~~~~~~~~~~~~~~~
348
349                    // ~~~~~~~~~~~~~~~~~~~~~~~~
350                    // n-recurrent terms begin.
351                    // ~~~~~~~~~~~~~~~~~~~~~~~~
352                    for r_index in 0..$RANK {
353                        // r_index: recursion index (j in handwritten note)
354                        let next_n_tuple = {
355                            let mut new_n_tuple = n_tuple.clone();
356                            new_n_tuple.iter_mut().enumerate().for_each(|(t, n)| {
357                                if t == r_index { *n += 1 }
358                            });
359                            new_n_tuple
360                        };
361                        if !remaining_tuples.shift_remove(&(l_tuple, next_n_tuple)) {
362                            continue
363                        }
364
365                        (0..3).for_each(|i| {
366                            // (rg - r_j) * [[:|:]]
367                            // rg is primitive-combination-specific.
368                            ints_r[i][l_tuple][next_n_tuple] = Some(
369                                self.rg.map(|r| dtype::from(r[i] - self.rs[r_index][i]))
370                                * ints_r[i][l_tuple][n_tuple].as_ref().unwrap_or_else(|| {
371                                    panic!("({l_tuple:?}, {n_tuple:?}) => ({l_tuple:?}, {next_n_tuple:?}) failed.")
372                                })
373                            );
374                        });
375
376                        (0..$RANK).for_each(|k| {
377                            // if let Some(kk) = self.ks[k].as_ref() {
378                            //     // 1 / (2 * zg) * sum(i) ii * k_iα * [[:|:]]
379                            //     // zg is primitive-combination-specific.
380                            //     (0..3).for_each(|i| {
381                            //         let add_term = self.zg.mapv(|zg| {
382                            //             C128::i() * kk[i] / (2.0 * zg)
383                            //         }) * ints_r[i][l_tuple][n_tuple].as_ref().unwrap_or_else(|| {
384                            //             panic!("({l_tuple:?}, {n_tuple:?}) => ({l_tuple:?}, {next_n_tuple:?}) failed.")
385                            //         });
386                            //         if let Some(arr) = ints_r[i][l_tuple][next_n_tuple].as_mut() {
387                            //             Zip::from(arr).and(&add_term).for_each(|a, &t| *a += t);
388                            //         } else {
389                            //             ints_r[i][l_tuple][next_n_tuple] = Some(add_term);
390                            //         }
391                            //     });
392                            // };
393                            if let Some(n_recur_k_term_kk) = self.ks[k].as_ref() {
394                                n_recur_k_term_func
395                            }
396
397                            if n_tuple[k] > 0 {
398                                let mut prev_n_tuple_k = n_tuple.clone();
399                                prev_n_tuple_k.iter_mut().enumerate().for_each(|(t, n)| {
400                                    if t == k { *n -= 1 }
401                                });
402                                debug_assert!(!remaining_tuples.contains(&(l_tuple, prev_n_tuple_k)));
403                                // 1 / (2 * zg) * sum(i) Nα(n_i) * [[n_i - 1_α:|:]]
404                                (0..3).for_each(|i| {
405                                    let add_term = self.zg.mapv(|zg| {
406                                        dtype::from(1.0)
407                                        / (2.0 * zg)
408                                        * n_tuple[k]
409                                            .to_f64()
410                                            .unwrap_or_else(|| panic!("Unable to convert `n_tuple[k]` = {} to `f64`.", n_tuple[k]))
411                                    }) * ints_r[i][l_tuple][prev_n_tuple_k].as_ref().unwrap_or_else(|| {
412                                        panic!("({l_tuple:?}, {prev_n_tuple_k:?}) => ({l_tuple:?}, {next_n_tuple:?}) failed.")
413                                    });
414                                    if let Some(arr) = ints_r[i][l_tuple][next_n_tuple].as_mut() {
415                                        Zip::from(arr).and(&add_term).for_each(|a, &t| *a += t);
416                                    } else {
417                                        ints_r[i][l_tuple][next_n_tuple] = Some(add_term);
418                                    }
419                                });
420                            };
421                        });
422
423                        if l_tuple[r_index] > 0 {
424                            let mut prev_l_tuple = l_tuple.clone();
425                            prev_l_tuple.iter_mut().enumerate().for_each(|(t, l)| {
426                                if t == r_index { *l -= 1 }
427                            });
428                            debug_assert!(!remaining_tuples.contains(&(prev_l_tuple, n_tuple)));
429                            // -Nα(l_j) * [[:l_j - 1_α|:]]
430                            // Note that Nα(l_j) = (l_j)_α.
431                            (0..3).for_each(|i| {
432                                let add_term = dtype::from(l_tuple[r_index]
433                                    .to_f64()
434                                    .unwrap_or_else(|| panic!("Unable to convert `l_tuple[r_index]` = {} to `f64`.", l_tuple[r_index]))
435                                )
436                                * ints_r[i][prev_l_tuple][n_tuple]
437                                    .as_ref()
438                                    .unwrap_or_else(|| {
439                                        panic!("({prev_l_tuple:?}, {n_tuple:?}) => ({l_tuple:?}, {next_n_tuple:?}) failed.")
440                                    });
441                                if let Some(arr) = ints_r[i][l_tuple][next_n_tuple].as_mut() {
442                                    Zip::from(arr).and(&add_term).for_each(|a, &t| *a -= t);
443                                } else {
444                                    ints_r[i][l_tuple][next_n_tuple] = Some(-add_term);
445                                }
446                            });
447                        }
448
449                        (0..$RANK).for_each(|k| {
450                            if l_tuple[k] > 0 {
451                                let mut prev_l_tuple_k = l_tuple.clone();
452                                prev_l_tuple_k.iter_mut().enumerate().for_each(|(t, l)| {
453                                    if t == k { *l -= 1 }
454                                });
455                                debug_assert!(!remaining_tuples.contains(&(prev_l_tuple_k, n_tuple)));
456                                // (1 / zg) * sum(g) z_g * Nα(l_g) * [[:l_g - 1_α|:]]
457                                (0..3).for_each(|i| {
458                                    let add_term = dtype::from(
459                                        l_tuple[k]
460                                            .to_f64()
461                                            .unwrap_or_else(|| panic!("Unable to convert `l_tuple[k]` = {} to `f64`.", l_tuple[k])))
462                                    / zg_type
463                                    * &self.zs[k] // broadcasting zs[k] to the shape of zg.
464                                    * ints_r[i][prev_l_tuple_k][n_tuple].as_ref().unwrap_or_else(|| {
465                                        panic!("({prev_l_tuple_k:?}, {n_tuple:?}) => ({l_tuple:?}, {next_n_tuple:?}) failed.")
466                                    });
467                                    if let Some(arr) = ints_r[i][l_tuple][next_n_tuple].as_mut() {
468                                        Zip::from(arr).and(&add_term).for_each(|a, &t| *a += t);
469                                    } else {
470                                        ints_r[i][l_tuple][next_n_tuple] = Some(add_term);
471                                    }
472                                });
473                            }
474                        })
475                    }
476                    // ~~~~~~~~~~~~~~~~~~~~~~
477                    // n-recurrent terms end.
478                    // ~~~~~~~~~~~~~~~~~~~~~~
479
480                    // ~~~~~~~~~~~~~~~~~~~~~~~~
481                    // l-recurrent terms begin.
482                    // ~~~~~~~~~~~~~~~~~~~~~~~~
483                    if extra_tuples.contains(&(l_tuple, n_tuple)) {
484                        continue
485                    }
486                    for r_index in 0..$RANK {
487                        // r_index: recursion index (g in handwritten note)
488                        let next_l_tuple = {
489                            let mut new_l_tuple = l_tuple.clone();
490                            new_l_tuple.iter_mut().enumerate().for_each(|(t, l)| {
491                                if t == r_index { *l += 1 }
492                            });
493                            new_l_tuple
494                        };
495                        if !remaining_tuples.shift_remove(&(next_l_tuple, n_tuple)) {
496                            continue
497                        }
498
499                        if let Some(l_recur_k_term_kr) = self.ks[r_index].as_ref() {
500                            // // -ii * k_gα * [[:|:]]
501                            // (0..3).for_each(|i| {
502                            //     let add_term = C128::i()
503                            //     * kr[i]
504                            //     * ints_r[i][l_tuple][n_tuple].as_ref().unwrap_or_else(|| {
505                            //         panic!("({l_tuple:?}, {n_tuple:?}) => ({next_l_tuple:?}, {n_tuple:?}) failed.")
506                            //     });
507                            //     if let Some(arr) = ints_r[i][next_l_tuple][n_tuple].as_mut() {
508                            //         Zip::from(arr).and(&add_term).for_each(|a, &t| *a -= t);
509                            //     } else {
510                            //         ints_r[i][next_l_tuple][n_tuple] = Some(-add_term);
511                            //     }
512                            // });
513                            l_recur_k_term_func
514                        }
515
516                        let next_n_tuple = {
517                            let mut new_n_tuple = n_tuple.clone();
518                            new_n_tuple.iter_mut().enumerate().for_each(|(t, n)| {
519                                if t == r_index { *n += 1 }
520                            });
521                            new_n_tuple
522                        };
523                        debug_assert!(next_n_tuple.iter().enumerate().all(|(t, n)| *n <= ns[t]));
524                        debug_assert!(!remaining_tuples.contains(&(l_tuple, next_n_tuple)));
525
526                        // 2 * z_g * [[n_g + 1_α:|:]]
527                        (0..3).for_each(|i| {
528                            let add_term = dtype::from(2.0)
529                            * ints_r[i][l_tuple][next_n_tuple].as_ref().unwrap_or_else(|| {
530                                panic!("({l_tuple:?}, {next_n_tuple:?}) => ({next_l_tuple:?}, {n_tuple:?}) failed.")
531                            })
532                            * &self.zs[r_index]; // broadcasting zs[r_index] to the shape of
533                                                 // ints_r[i][l_tuple][next_n_tuple].
534                            if let Some(arr) = ints_r[i][next_l_tuple][n_tuple].as_mut() {
535                                Zip::from(arr).and(&add_term).for_each(|a, &t| *a += t);
536                            } else {
537                                ints_r[i][next_l_tuple][n_tuple] = Some(add_term);
538                            }
539                        });
540
541                        if n_tuple[r_index] > 0 {
542                            let mut prev_n_tuple = n_tuple.clone();
543                            prev_n_tuple.iter_mut().enumerate().for_each(|(t, n)| {
544                                if t == r_index { *n -= 1 }
545                            });
546                            debug_assert!(!remaining_tuples.contains(&(l_tuple, prev_n_tuple)));
547
548                            // -Nα(n_g) * [[n_g - 1_α:|:]]
549                            (0..3).for_each(|i| {
550                                let add_term = dtype::from(
551                                    n_tuple[r_index]
552                                        .to_f64()
553                                        .unwrap_or_else(|| panic!("Unable to convert `n_tuple[r_index]` = {} to `f64`.", n_tuple[r_index]))
554                                )
555                                * ints_r[i][l_tuple][prev_n_tuple].as_ref().unwrap_or_else(|| {
556                                    panic!("({l_tuple:?}, {prev_n_tuple:?}) => ({next_l_tuple:?}, {n_tuple:?}) failed.")
557                                });
558                                if let Some(arr) = ints_r[i][next_l_tuple][n_tuple].as_mut() {
559                                    Zip::from(arr).and(&add_term).for_each(|a, &t| *a -= t);
560                                } else {
561                                    ints_r[i][next_l_tuple][n_tuple] = Some(-add_term);
562                                }
563                            });
564                        }
565                    }
566                    // ~~~~~~~~~~~~~~~~~~~~~~
567                    // l-recurrent terms end.
568                    // ~~~~~~~~~~~~~~~~~~~~~~
569                }
570                // ~~~~~~~~~~~~~~~~~~~~~~~~~~
571                // Loop over all tuples ends.
572                // ~~~~~~~~~~~~~~~~~~~~~~~~~~
573
574                // ~~~~~~~~~~~~~~~~~~~~~
575                // Normalisation begins.
576                // ~~~~~~~~~~~~~~~~~~~~~
577                // Each element of `norm_arr` gives the scaling factor to be multiplied by an
578                // integral involving a specific combination of primitives (i.e. in the integral,
579                // each centre comprises a single Gaussian function) over one of the three
580                // Cartesian directions, such that each primitive is normalised and that the
581                // two-centre integrals〈g0|g0〉is precisely one.
582                // The elements of `norm_arr` are only dependent on the exponents, not the
583                // contraction coefficients, because `g0` is taken to have a coefficient of 1.
584                // Contraction coefficients are introduced later.
585                for n_tuple in n_tuples.iter() {
586                    let rank_i32 = $RANK
587                        .to_i32()
588                        .expect("Unable to convert the tuple rank to `i32`.");
589                    let norm_arr =
590                        (2.0 / std::f64::consts::PI).sqrt().sqrt().powi(rank_i32)
591                        * n_tuple.iter().map(|n| {
592                            let doufac = if *n == 0 {
593                                1
594                            } else {
595                                ((2 * n) - 1)
596                                    .checked_double_factorial()
597                                    .unwrap_or_else(|| panic!("Unable to obtain `{}!!`.", 2 * n - 1))
598                            }
599                            .to_f64()
600                            .unwrap_or_else(|| panic!("Unable to convert `{}!!` to `f64`.", 2 * n - 1));
601                            1.0 / doufac.sqrt()
602                        }).product::<f64>()
603                        * self.zd.map(|zd| zd.sqrt().sqrt());
604                    let norm_arr = self
605                        .zs
606                        .iter()
607                        .zip(n_tuple.iter())
608                        .enumerate()
609                        .fold(norm_arr, |acc, (j, (z, n))| {
610                            let mut shape = [$(replace_expr!(($shell_name) 1)),+];
611                            shape[j] = z.len();
612                            let z_transformed = z.mapv(|z_val| {
613                                if n.rem_euclid(2) == 0 {
614                                    (4.0 * z_val).powi(
615                                        (n.div_euclid(2))
616                                            .to_i32()
617                                            .expect("Unable to convert `n` to `i32`.")
618                                    )
619                                } else {
620                                    (4.0 * z_val).powf(
621                                        n.to_f64().expect("Unable to convert `n` to `f64`.")
622                                        / 2.0
623                                    )
624                                }
625                            })
626                            .into_shape_with_order(shape)
627                            .expect("Unable to convert transformed `z` to {$RANK} dimensions.");
628                            acc * z_transformed
629                        });
630
631                    for l_tuple in l_tuples.iter() {
632                        (0..3).for_each(|i| {
633                            if let Some(arr) = ints_r[i][*l_tuple][*n_tuple].as_mut() {
634                                Zip::from(arr)
635                                    .and(&norm_arr)
636                                    .for_each(|a, &n| *a *= dtype::from(n));
637                            }
638                        });
639                    }
640                }
641                // ~~~~~~~~~~~~~~~~~~~
642                // Normalisation ends.
643                // ~~~~~~~~~~~~~~~~~~~
644
645                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
646                // Population of Cartesian integrals for each derivative component begins.
647                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
648                let lex_cart_orders = (0..=*ls.iter().max().expect("Unable to determine the maximum derivative order."))
649                    .map(|l| CartOrder::lex(u32::try_from(l).expect("Unable to convert a derivative order to `u32`.")))
650                    .collect::<Vec<_>>();
651                let cart_shell_shape = {
652                    let mut cart_shell_shape_iter = self
653                        .ns
654                        .iter()
655                        .map(|n| ((n + 1) * (n + 2)).div_euclid(2));
656                    $(
657                        let $shell_name = cart_shell_shape_iter
658                            .next()
659                            .expect("cart_shell_shape out of range.");
660                    )+
661                    [$($shell_name),+]
662                };
663                let all_shells_contraction_str = (0..$RANK)
664                    .map(|i| (i.to_u8().expect("Unable to convert a shell index to `u8`.") + 97) as char)
665                    .collect::<String>();
666                let shell_blocks = ls
667                    .iter()
668                    .map(|l| 0..((l + 1) * (l + 2)).div_euclid(2))
669                    .multi_cartesian_product()
670                    .map(|l_indices| {
671                        // ls = [m, n, p, ...]
672                        // l_indices = [a, b, c, ...]
673                        //   - a-th component (lexicographic order) of the m-th derivative of the first shell,
674                        //   - b-th component (lexicographic order) of the n-th derivative of the second shell,
675                        //   - etc.
676                        // The derivative components are arranged in lexicographic Cartersian order.
677                        // If ls = [0, 1, 2], then the a particular l_indices could take the value
678                        // [0, 2, 3] which represents
679                        //   - 0th derivative of the first shell
680                        //   - d/dz of the second shell (x, y, z)
681                        //   - d2/dyy of the third shell (xx, xy, xz, yy, yz, zz)
682                        debug_assert_eq!(l_indices.len(), $RANK);
683                        let mut l_indices_iter = l_indices.into_iter();
684                        $(
685                            let $shell_name = l_indices_iter
686                                .next()
687                                .expect("l index out of range.");
688                        )+
689                        let l_indices = [$($shell_name),+];
690
691                        // l_powers translates l_indices into tuples of component derivative orders
692                        // for each shell.
693                        // For example, with ls = [0, 1, 2] and l_indices = [0, 2, 3],
694                        // l_powers is given by [(0, 0, 0), (0, 0, 1), (0, 2, 0)].
695                        let l_powers = {
696                            let mut l_powers_mut = [$(
697                                replace_expr!(($shell_name) (0, 0, 0))
698                            ),+];
699                            l_powers_mut.iter_mut().enumerate().for_each(|(shell_index, l_power)| {
700                                *l_power = lex_cart_orders[ls[shell_index]].cart_tuples[l_indices[shell_index]].clone();
701                            });
702                            l_powers_mut
703                        };
704
705                        // l_tuples_xyz gives l_tuple for each Cartesian component.
706                        // With l_powers [(0, 0, 0), (0, 0, 1), (0, 2, 0)],
707                        // l_tuples_xyz is given by
708                        // [(0, 0, 0), (0, 0, 2), (0, 1, 0)]
709                        //  ----x----  ----y----  ----z----
710                        // which means: take the product of the (0, 0, 0) x-derivative,
711                        // (0, 0, 2) y-derivative, and (0, 1, 0) z-derivative to give int_xyz.
712                        // Essentially, l_tuples_xyz is transposed l_powers.
713                        // l_tuples_xyz will be cloned inside the for loop below because it
714                        // is consumed after every iteration.
715                        let outer_l_tuples_xyz = {
716                            let mut l_tuples_xyz_mut = [
717                                [$(replace_expr!(($shell_name) 0usize)),+]; 3
718                            ];
719                            l_tuples_xyz_mut[0].iter_mut().enumerate().for_each(|(shell_index, l)| {
720                                *l = usize::try_from(l_powers[shell_index].0)
721                                    .expect("Unable to convert `l` to `usize`.");
722                            });
723                            l_tuples_xyz_mut[1].iter_mut().enumerate().for_each(|(shell_index, l)| {
724                                *l = usize::try_from(l_powers[shell_index].1)
725                                    .expect("Unable to convert `l` to `usize`.");
726                            });
727                            l_tuples_xyz_mut[2].iter_mut().enumerate().for_each(|(shell_index, l)| {
728                                *l = usize::try_from(l_powers[shell_index].2)
729                                    .expect("Unable to convert `l` to `usize`.");
730                            });
731                            l_tuples_xyz_mut
732                        };
733
734                        let mut cart_shell_block = Array::<dtype, Dim<[usize; $RANK]>>::zeros(
735                            cart_shell_shape
736                        );
737                        for cart_indices in cart_shell_shape.iter().map(|d| 0..*d).multi_cartesian_product() {
738                            // cart_indices = [i, j, k, l, ...]
739                            //   - i-th Cartesian component (shell's specified order) of the first shell,
740                            //   - j-th Cartesian component (shell's specified order) of the second shell,
741                            //   - etc.
742                            // If a shell has pure ordering, a lexicographic Cartesian order will
743                            // be used. Integrals involving this shell will be converted back to
744                            // pure form later.
745                            // If shell_tuple.ns = [0, 2, 3, 1], then the a particular cart_indices could
746                            // take the value [0, 2, 10, 1] which represents
747                            //   - s function on the first shell
748                            //   - dxz function on the second shell
749                            //   - fzzz function on the third shell
750                            //   - py function on the fourth shell
751                            let mut cart_indices_iter = cart_indices.into_iter();
752                            $(
753                                let $shell_name = cart_indices_iter
754                                    .next()
755                                    .expect("cart_index out of range.");
756                            )+
757                            let cart_indices = [$($shell_name),+];
758
759                            // cart_powers translates cart_indices into tuples of Cartesian powers
760                            // for each shell.
761                            // For example, with shell_tuple.ns = (0, 2, 3, 1) and
762                            // cart_indices = (0, 2, 10, 1), cart_powers is given by
763                            // [(0, 0, 0), (1, 0, 1), (0, 0, 3), (0, 1, 0)] (assuming
764                            // lexicographic ordering).
765                            let cart_powers = {
766                                let mut cart_powers_mut = [$(
767                                    replace_expr!(($shell_name) (0, 0, 0))
768                                ),+];
769                                cart_powers_mut.iter_mut().enumerate().for_each(|(shell_index, cart_power)| {
770                                    let cart_order = match &self
771                                        .shells[shell_index].0
772                                        .basis_shell()
773                                        .shell_order {
774                                            ShellOrder::Pure(po) => CartOrder::lex(po.lpure),
775                                            ShellOrder::Cart(co) => co.clone(),
776                                            ShellOrder::Spinor(_) => panic!("Cartesian orders cannot be constructed for a spinor shell.")
777                                        };
778                                    *cart_power = cart_order
779                                        .cart_tuples[cart_indices[shell_index]]
780                                        .clone();
781                                });
782                                cart_powers_mut
783                            };
784
785                            // n_tuples_xyz gives n_tuple for each Cartesian component.
786                            // With cart_powers = [(0, 0, 0), (1, 0, 1), (0, 0, 3), (0, 1, 0)],
787                            // n_tuples_xyz is given by
788                            // [(0, 1, 0, 0), (0, 0, 0, 1), (0, 1, 3, 0)]
789                            //  -----x------  -----y------  -----z------
790                            // which means: take the product of the (0, 1, 0, 0) x-integral,
791                            // (0, 0, 0, 1) y-integral, and (0, 1, 3, 0) z-integral to give int_xyz.
792                            // Essentially, n_tuples_xyz is transposed cart_powers.
793                            let l_tuples_xyz = outer_l_tuples_xyz.clone();
794                            let n_tuples_xyz = {
795                                let mut n_tuples_xyz_mut = [[$(replace_expr!(($shell_name) 0usize)),+]; 3];
796                                n_tuples_xyz_mut[0].iter_mut().enumerate().for_each(|(shell_index, n)| {
797                                    *n = usize::try_from(cart_powers[shell_index].0)
798                                        .expect("Unable to convert `n` to `usize`.");
799                                });
800                                n_tuples_xyz_mut[1].iter_mut().enumerate().for_each(|(shell_index, n)| {
801                                    *n = usize::try_from(cart_powers[shell_index].1)
802                                        .expect("Unable to convert `n` to `usize`.");
803                                });
804                                n_tuples_xyz_mut[2].iter_mut().enumerate().for_each(|(shell_index, n)| {
805                                    *n = usize::try_from(cart_powers[shell_index].2)
806                                        .expect("Unable to convert `n` to `usize`.");
807                                });
808                                n_tuples_xyz_mut
809                            };
810                            let int_xyz = izip!(l_tuples_xyz.iter(), n_tuples_xyz.iter())
811                                .enumerate()
812                                .map(|(i, (l_tuple, n_tuple))| {
813                                    ints_r[i][*l_tuple][*n_tuple].as_ref()
814                                })
815                                .collect::<Option<Vec<_>>>()
816                                .map(|arrs| arrs.into_iter().fold(
817                                    Array::<dtype, Dim<[usize; $RANK]>>::ones(
818                                        self.primitive_shell_shape
819                                    ),
820                                    |acc, arr| acc * arr
821                                ))
822                                .unwrap_or_else(
823                                    || Array::<dtype, Dim<[usize; $RANK]>>::zeros(
824                                        self.primitive_shell_shape
825                                    )
826                                );
827
828                            // Contraction coefficients are involved here.
829                            cart_shell_block[cart_indices] = einsum(
830                                &format!("{all_shells_contraction_str},{all_shells_contraction_str}->"),
831                                &[&int_xyz, dd_type]
832                            )
833                                .expect("Unable to contract `int_xyz` with `dd`.")
834                                .into_iter()
835                                .next()
836                                .expect("Unable to retrieve the result of the contraction between `int_xyz` and `dd`.");
837                        }
838
839                        // Transform some shells to spherical if necessary
840                        if (0..$RANK).any(|i| matches!(self.shells[i].0.basis_shell().shell_order, ShellOrder::Pure(_))) {
841                            // We need an extra letter for the contraction axis.
842                            assert!($RANK < 26);
843                            let rank_u8 = $RANK.to_u8().expect("Unable to convert the shell tuple rank to `u8`.");
844                            let transformed_shell_block = (0..$RANK)
845                                .fold(cart_shell_block, |acc, i| {
846                                    if let ShellOrder::Pure(_) = self.shells[i].0.basis_shell().shell_order {
847                                        let i_u8 = i.to_u8().expect("Unable to convert a shell index to `u8`.");
848                                        let rl2cart = rl2cart_type;
849                                        let cart_to_pure_contraction_str = format!(
850                                            "{}{}",
851                                            (i_u8 + 97) as char,
852                                            (i_u8 + 97 + rank_u8) as char,
853                                        );
854                                        let result_str = (0..$RANK).map(|j| {
855                                            if j == i {
856                                                (i_u8 + 97 + rank_u8) as char
857                                            } else {
858                                                let j_u8 = j.to_u8().expect("Unable to convert a shell index to `u8`.");
859                                                (j_u8 + 97) as char
860                                            }
861                                        }).collect::<String>();
862                                        einsum(
863                                            &format!(
864                                                "{all_shells_contraction_str},\
865                                                {cart_to_pure_contraction_str}->\
866                                                {result_str}"
867                                            ),
868                                            &[&acc, rl2cart]
869                                        )
870                                        .unwrap_or_else(|_| panic!("Unable to convert shell {i} to spherical order."))
871                                        .into_dimensionality::<Dim<[usize; $RANK]>>()
872                                        .unwrap_or_else(|_| panic!("Unable to convert the transformed shell block into the correct shape."))
873                                    } else {
874                                        acc
875                                    }
876                                });
877                            transformed_shell_block
878                        } else {
879                            cart_shell_block
880                        }
881                    }).collect::<Vec<_>>();
882                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
883                // Population of Cartesian integrals for each derivative component ends.
884                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
885
886                shell_blocks
887            }
888        }
889
890        #[duplicate_item(
891            [
892                dtype [ f64 ]
893            ]
894            [
895                dtype [ C128 ]
896            ]
897        )]
898        impl<'a> ShellTupleCollection<'a, $RANK, dtype> {
899            /// Calculates the overlap arrays for this shell tuple collection.
900            ///
901            /// # Arguments
902            ///
903            /// * `ls` - The derivative pattern.
904            ///
905            /// # Returns
906            ///
907            /// A vector of overlap arrays, each of which is for one derivative component.
908            pub fn overlap(
909                &self, ls: [usize; $RANK]
910            ) -> Vec<Array<dtype, Dim<[usize; $RANK]>>> {
911                let lex_cart_orders = (0..=*ls.iter().max().expect("Unable to determine the maximum derivative order."))
912                    .map(|l| CartOrder::lex(u32::try_from(l).expect("Unable to convert a derivative order to `u32`.")))
913                    .collect::<Vec<_>>();
914
915                // shell_blockss = [shell_blocks0, shell_blocks1, ...]
916                //   shell_blocks0: all derivative components for shell tuple 0
917                //   shell_blocks0 = [shell_block00, shell_block11, ...]
918                //     shell_block01: first derivative component for shell tuple 0
919                let shell_blockss = self
920                    .unique_shell_tuples_iter(ls)
921                    .par_bridge()
922                    .map(|(shell_tuple, unique_perm, equiv_perms)| {
923                        log::debug!("Working on unique permutation: {unique_perm:?}");
924                        (shell_tuple.overlap(ls), unique_perm, equiv_perms)
925                    })
926                    .collect::<Vec<_>>();
927
928                // Pack integrals
929                let intss = ls
930                    .iter()
931                    .map(|l| 0..(((l + 1) * (l + 2)).div_euclid(2)))
932                    .multi_cartesian_product()
933                    .enumerate()
934                    .map(|(l_component_index, l_indices)| {
935                        let mut l_indices_iter = l_indices.into_iter();
936                        $(
937                            let $shell_name = l_indices_iter
938                                .next()
939                                .expect("l index out of range.");
940                        )+
941                        let l_indices = [$($shell_name),+];
942
943                        // l_powers translates l_indices into tuples of component derivative orders
944                        // for each shell.
945                        // For example, with ls = [0, 1, 2] and l_indices = [0, 2, 3],
946                        // l_powers is given by [(0, 0, 0), (0, 0, 1), (0, 2, 0)].
947                        let l_powers = {
948                            let mut l_powers_mut = [$(
949                                replace_expr!(($shell_name) (0, 0, 0))
950                            ),+];
951                            l_powers_mut.iter_mut().enumerate().for_each(|(shell_index, l_power)| {
952                                *l_power = lex_cart_orders[ls[shell_index]].cart_tuples[l_indices[shell_index]].clone();
953                            });
954                            l_powers_mut
955                        };
956                        log::debug!("Component {l_component_index} is for derivative {l_powers:?}.");
957                        let mut ints = Array::<dtype, Dim<[usize; $RANK]>>::zeros(
958                            self.angular_all_shell_shape
959                        );
960
961                        shell_blockss.iter().for_each(|(shell_blocks, unique_perm, equiv_perms)| {
962                            equiv_perms.iter().for_each(|&equiv_perm| {
963                                let mut unique_perm_vec = unique_perm
964                                    .iter()
965                                    .map(|i| Some(*i))
966                                    .collect::<Vec<_>>();
967                                let mut transpose_indices_iter = equiv_perm.iter().map(|i| {
968                                    let index = unique_perm_vec
969                                        .iter()
970                                        .position(|&j| j == Some(*i))
971                                        .unwrap_or_else(|| {
972                                            panic!("Unable to find a permutation that maps {unique_perm:?} to {equiv_perm:?}.");
973                                        });
974                                    unique_perm_vec[index] = None;
975                                    index
976                                });
977                                $(
978                                    let $shell_name = transpose_indices_iter
979                                        .next()
980                                        .expect("Shell index out of range.");
981                                )+
982                                let transpose_indices = [$($shell_name),+];
983
984                                let mut shell_boundaries_iter = equiv_perm
985                                    .iter()
986                                    .enumerate()
987                                    .map(|(shell_index, &i)| {
988                                        self.basis_sets[shell_index].shell_boundaries()[i]
989                                    });
990                                $(
991                                    let $shell_name = shell_boundaries_iter
992                                        .next()
993                                        .expect("Shell index out of range.");
994                                )+
995                                let shell_slices = s![$(
996                                    $shell_name.0..$shell_name.1
997                                ),+];
998                                ints.slice_mut(shell_slices).assign(
999                                    &shell_blocks[l_component_index]
1000                                        .clone()
1001                                        .permuted_axes(transpose_indices)
1002                                );
1003                            })
1004                        });
1005                        ints
1006                    }).collect::<Vec<_>>();
1007
1008                intss
1009            }
1010        }
1011    }
1012}
1013
1014pub(crate) use impl_shell_tuple_overlap;