|
15 | 15 | - Ahlrichs, R. Phys. Chem. Chem. Phys. 2006, 8, 3072. |
16 | 16 | """ |
17 | 17 |
|
| 18 | +import functools |
| 19 | + |
18 | 20 | import numpy as np |
19 | 21 |
|
20 | 22 | from gbasis.utils import factorial2 |
21 | 23 |
|
22 | | -# Cache for factorial2 values to avoid repeated computation |
23 | | -_FACTORIAL2_CACHE = {} |
24 | | - |
25 | 24 |
|
26 | | -def _get_factorial2_norm(angmom_components): |
| 25 | +@functools.cache |
| 26 | +def _get_factorial2_norm(angmom_key): |
27 | 27 | """Get cached factorial2 normalization for angular momentum components. |
28 | 28 |
|
29 | 29 | Parameters |
30 | 30 | ---------- |
31 | | - angmom_components : np.ndarray(n, 3) |
32 | | - Angular momentum components. |
| 31 | + angmom_key : tuple of tuples |
| 32 | + Angular momentum components as a tuple of tuples, e.g. |
| 33 | + ((lx1, ly1, lz1), (lx2, ly2, lz2), ...). |
33 | 34 |
|
34 | 35 | Returns |
35 | 36 | ------- |
36 | 37 | norm : np.ndarray(n,) |
37 | 38 | Normalization factors 1/sqrt(prod((2*l-1)!!)). |
38 | 39 | """ |
39 | | - key = tuple(map(tuple, angmom_components)) |
40 | | - if key not in _FACTORIAL2_CACHE: |
41 | | - _FACTORIAL2_CACHE[key] = 1.0 / np.sqrt( |
42 | | - np.prod(factorial2(2 * angmom_components - 1), axis=1) |
43 | | - ) |
44 | | - return _FACTORIAL2_CACHE[key] |
| 40 | + angmom_components = np.array(angmom_key) |
| 41 | + return 1.0 / np.sqrt(np.prod(factorial2(2 * angmom_components - 1), axis=1)) |
45 | 42 |
|
46 | 43 |
|
47 | | -def _optimized_contraction( |
48 | | - integrals_etransf, |
49 | | - exps_a, |
50 | | - exps_b, |
51 | | - exps_c, |
52 | | - exps_d, |
53 | | - coeffs_a, |
54 | | - coeffs_b, |
55 | | - coeffs_c, |
56 | | - coeffs_d, |
57 | | - angmom_a, |
58 | | - angmom_b, |
59 | | - angmom_c, |
60 | | - angmom_d, |
61 | | -): |
| 44 | +def _optimized_contraction(integrals_etransf, exps, coeffs, angmoms): |
62 | 45 | """Optimized primitive contraction using einsum. |
63 | 46 |
|
64 | 47 | Parameters |
65 | 48 | ---------- |
66 | 49 | integrals_etransf : np.ndarray |
67 | 50 | ETR output with shape (c_x, c_y, c_z, a_x, a_y, a_z, K_d, K_b, K_c, K_a). |
68 | | - exps_a/b/c/d : np.ndarray |
69 | | - Primitive exponents. |
70 | | - coeffs_a/b/c/d : np.ndarray |
71 | | - Contraction coefficients. |
72 | | - angmom_a/b/c/d : int |
73 | | - Angular momenta. |
| 51 | + exps : array-like of shape (4, K) |
| 52 | + Primitive exponents stacked for all 4 centers (a, b, c, d). |
| 53 | + coeffs : array-like of shape (4, K, M) |
| 54 | + Contraction coefficients stacked for all 4 centers (a, b, c, d). |
| 55 | + angmoms : array-like of shape (4,) |
| 56 | + Angular momenta for all 4 centers (a, b, c, d). |
74 | 57 |
|
75 | 58 | Returns |
76 | 59 | ------- |
77 | 60 | contracted : np.ndarray |
78 | 61 | Contracted integrals with shape (c_x, c_y, c_z, a_x, a_y, a_z, M_a, M_c, M_b, M_d). |
79 | 62 | """ |
80 | | - # Precompute normalization constants (1D arrays) |
81 | | - norm_a = (2 * exps_a / np.pi) ** 0.75 * (4 * exps_a) ** (angmom_a / 2) |
82 | | - norm_b = (2 * exps_b / np.pi) ** 0.75 * (4 * exps_b) ** (angmom_b / 2) |
83 | | - norm_c = (2 * exps_c / np.pi) ** 0.75 * (4 * exps_c) ** (angmom_c / 2) |
84 | | - norm_d = (2 * exps_d / np.pi) ** 0.75 * (4 * exps_d) ** (angmom_d / 2) |
85 | | - |
86 | | - # Multiply coefficients by normalization (more efficient than per-element) |
87 | | - coeffs_a_norm = coeffs_a * norm_a[:, np.newaxis] |
88 | | - coeffs_b_norm = coeffs_b * norm_b[:, np.newaxis] |
89 | | - coeffs_c_norm = coeffs_c * norm_c[:, np.newaxis] |
90 | | - coeffs_d_norm = coeffs_d * norm_d[:, np.newaxis] |
| 63 | + # Compute norms per center (supports different K per center) |
| 64 | + norms = [((2 / np.pi) * e) ** 0.75 * (4 * e) ** (ang / 2) for e, ang in zip(exps, angmoms)] |
| 65 | + coeffs_norm = [c * n[:, np.newaxis] for c, n in zip(coeffs, norms)] |
| 66 | + coeffs_a_norm, coeffs_b_norm, coeffs_c_norm, coeffs_d_norm = coeffs_norm |
91 | 67 |
|
92 | 68 | # Use einsum with optimization for contraction |
93 | 69 | # Input: (c_x, c_y, c_z, a_x, a_y, a_z, K_d, K_b, K_c, K_a) |
@@ -594,10 +570,18 @@ def _horizontal_recursion_relation( |
594 | 570 | integrals = np.transpose(integrals_horiz_b2, (1, 0, 3, 2, 4, 6, 5, 7)) |
595 | 571 |
|
596 | 572 | # Apply factorial2 normalization for angular momentum components |
597 | | - norm_a = _get_factorial2_norm(angmom_components_a).reshape(-1, 1, 1, 1, 1, 1, 1, 1) |
598 | | - norm_b = _get_factorial2_norm(angmom_components_b).reshape(1, -1, 1, 1, 1, 1, 1, 1) |
599 | | - norm_c = _get_factorial2_norm(angmom_components_c).reshape(1, 1, -1, 1, 1, 1, 1, 1) |
600 | | - norm_d = _get_factorial2_norm(angmom_components_d).reshape(1, 1, 1, -1, 1, 1, 1, 1) |
| 573 | + norm_a = _get_factorial2_norm(tuple(map(tuple, angmom_components_a))).reshape( |
| 574 | + -1, 1, 1, 1, 1, 1, 1, 1 |
| 575 | + ) |
| 576 | + norm_b = _get_factorial2_norm(tuple(map(tuple, angmom_components_b))).reshape( |
| 577 | + 1, -1, 1, 1, 1, 1, 1, 1 |
| 578 | + ) |
| 579 | + norm_c = _get_factorial2_norm(tuple(map(tuple, angmom_components_c))).reshape( |
| 580 | + 1, 1, -1, 1, 1, 1, 1, 1 |
| 581 | + ) |
| 582 | + norm_d = _get_factorial2_norm(tuple(map(tuple, angmom_components_d))).reshape( |
| 583 | + 1, 1, 1, -1, 1, 1, 1, 1 |
| 584 | + ) |
601 | 585 |
|
602 | 586 | integrals = integrals * norm_a * norm_b * norm_c * norm_d |
603 | 587 |
|
@@ -725,18 +709,9 @@ def compute_two_electron_integrals_os_hgp( |
725 | 709 | # --- Step 4: Contract primitives --- |
726 | 710 | integrals_cont = _optimized_contraction( |
727 | 711 | integrals_etransf, |
728 | | - exps_a, |
729 | | - exps_b, |
730 | | - exps_c, |
731 | | - exps_d, |
732 | | - coeffs_a, |
733 | | - coeffs_b, |
734 | | - coeffs_c, |
735 | | - coeffs_d, |
736 | | - angmom_a, |
737 | | - angmom_b, |
738 | | - angmom_c, |
739 | | - angmom_d, |
| 712 | + (exps_a, exps_b, exps_c, exps_d), |
| 713 | + (coeffs_a, coeffs_b, coeffs_c, coeffs_d), |
| 714 | + (angmom_a, angmom_b, angmom_c, angmom_d), |
740 | 715 | ) |
741 | 716 |
|
742 | 717 | # --- Step 5: HRR (done LAST per HGP scheme) --- |
|
0 commit comments