Skip to content

Commit 8c8f69c

Browse files
authored
Merge pull request #245 from San1357/feat/woc-weeks1-9
PR[9] (combine PR-1-PR-8) feat: combine weeks 1-9 implementations into single branch
2 parents 4705d32 + 104446c commit 8c8f69c

12 files changed

Lines changed: 4646 additions & 221 deletions
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
"""Integral screening utilities for efficient 2-electron integral computation.
2+
3+
This module implements Schwarz screening and shell-pair screening to skip
4+
negligible integrals, providing speedup for spatially extended systems.
5+
6+
References:
7+
- Häser, M. & Ahlrichs, R. J. Comput. Chem. 1989, 10, 104.
8+
- Gill, P. M. W.; Johnson, B. G.; Pople, J. A. Int. J. Quantum Chem. 1991, 40, 745.
9+
"""
10+
11+
import numpy as np
12+
13+
14+
def compute_schwarz_bound_shell_pair(boys_func, cont_one, cont_two, compute_integral_func):
15+
"""Compute Schwarz bound for a shell pair: sqrt((ab|ab)).
16+
17+
Parameters
18+
----------
19+
boys_func : callable
20+
Boys function for integral evaluation.
21+
cont_one : GeneralizedContractionShell
22+
First contracted shell.
23+
cont_two : GeneralizedContractionShell
24+
Second contracted shell.
25+
compute_integral_func : callable
26+
Function to compute (ab|cd) integrals.
27+
28+
Returns
29+
-------
30+
bound : float
31+
Schwarz bound sqrt(max|(ab|ab)|) for this shell pair.
32+
"""
33+
# Compute (ab|ab) integral
34+
integral = compute_integral_func(
35+
boys_func,
36+
cont_one.coord,
37+
cont_one.angmom,
38+
cont_one.angmom_components_cart,
39+
cont_one.exps,
40+
cont_one.coeffs,
41+
cont_two.coord,
42+
cont_two.angmom,
43+
cont_two.angmom_components_cart,
44+
cont_two.exps,
45+
cont_two.coeffs,
46+
cont_one.coord,
47+
cont_one.angmom,
48+
cont_one.angmom_components_cart,
49+
cont_one.exps,
50+
cont_one.coeffs,
51+
cont_two.coord,
52+
cont_two.angmom,
53+
cont_two.angmom_components_cart,
54+
cont_two.exps,
55+
cont_two.coeffs,
56+
)
57+
58+
# Return sqrt of maximum absolute value
59+
return np.sqrt(np.max(np.abs(integral)))
60+
61+
62+
def compute_schwarz_bounds(contractions, boys_func, compute_integral_func):
63+
"""Precompute Schwarz bounds for all shell pairs.
64+
65+
Parameters
66+
----------
67+
contractions : list of GeneralizedContractionShell
68+
List of all contracted shells.
69+
boys_func : callable
70+
Boys function for integral evaluation.
71+
compute_integral_func : callable
72+
Function to compute (ab|cd) integrals.
73+
74+
Returns
75+
-------
76+
bounds : np.ndarray(n_shells, n_shells)
77+
Schwarz bounds sqrt((ab|ab)) for each shell pair.
78+
"""
79+
n_shells = len(contractions)
80+
bounds = np.zeros((n_shells, n_shells))
81+
82+
for i, cont_i in enumerate(contractions):
83+
for j in range(i, n_shells):
84+
cont_j = contractions[j]
85+
bounds[i, j] = compute_schwarz_bound_shell_pair(
86+
boys_func, cont_i, cont_j, compute_integral_func
87+
)
88+
bounds[j, i] = bounds[i, j] # Symmetry: (ab|ab) = (ba|ba)
89+
90+
return bounds
91+
92+
93+
def shell_pair_significant(cont_one, cont_two, threshold=1e-12):
94+
"""Check if a shell pair is significant using primitive screening.
95+
96+
Uses the Gaussian product theorem: exp(-a*b/(a+b) * |A-B|^2) factor.
97+
If this factor is below threshold for all primitive pairs, skip.
98+
99+
Parameters
100+
----------
101+
cont_one : GeneralizedContractionShell
102+
First contracted shell.
103+
cont_two : GeneralizedContractionShell
104+
Second contracted shell.
105+
threshold : float
106+
Screening threshold.
107+
108+
Returns
109+
-------
110+
significant : bool
111+
True if shell pair might contribute significantly.
112+
"""
113+
# Distance between shell centers
114+
r_ab_sq = np.sum((cont_one.coord - cont_two.coord) ** 2)
115+
116+
if r_ab_sq < 1e-10:
117+
# Same center, always significant
118+
return True
119+
120+
# Check if any primitive pair survives screening
121+
for exp_a in cont_one.exps:
122+
for exp_b in cont_two.exps:
123+
# Gaussian decay factor
124+
decay = np.exp(-exp_a * exp_b / (exp_a + exp_b) * r_ab_sq)
125+
if decay > threshold:
126+
return True
127+
128+
return False
129+
130+
131+
class SchwarzScreener:
132+
"""Class for Schwarz integral screening.
133+
134+
Precomputes Schwarz bounds and provides efficient screening.
135+
136+
Attributes
137+
----------
138+
bounds : np.ndarray
139+
Schwarz bounds for all shell pairs.
140+
threshold : float
141+
Screening threshold.
142+
n_screened : int
143+
Counter for number of screened shell quartets.
144+
n_computed : int
145+
Counter for number of computed shell quartets.
146+
"""
147+
148+
def __init__(self, contractions, boys_func, compute_integral_func, threshold=1e-12):
149+
"""Initialize Schwarz screener.
150+
151+
Parameters
152+
----------
153+
contractions : list of GeneralizedContractionShell
154+
List of all contracted shells.
155+
boys_func : callable
156+
Boys function for integral evaluation.
157+
compute_integral_func : callable
158+
Function to compute (ab|cd) integrals.
159+
threshold : float
160+
Screening threshold (default: 1e-12).
161+
"""
162+
self.threshold = threshold
163+
self.n_screened = 0
164+
self.n_computed = 0
165+
166+
# Precompute Schwarz bounds
167+
self.bounds = compute_schwarz_bounds(contractions, boys_func, compute_integral_func)
168+
169+
def is_significant(self, i, j, k, l_shell):
170+
"""Check if shell quartet (ij|kl) is significant.
171+
172+
Uses Schwarz inequality: |(ij|kl)| <= sqrt((ij|ij)) * sqrt((kl|kl))
173+
174+
Parameters
175+
----------
176+
i, j, k, l_shell : int
177+
Shell indices.
178+
179+
Returns
180+
-------
181+
significant : bool
182+
True if integral might be significant, False if can be skipped.
183+
"""
184+
bound = self.bounds[i, j] * self.bounds[k, l_shell]
185+
186+
if bound < self.threshold:
187+
self.n_screened += 1
188+
return False
189+
else:
190+
self.n_computed += 1
191+
return True
192+
193+
def get_statistics(self):
194+
"""Get screening statistics.
195+
196+
Returns
197+
-------
198+
stats : dict
199+
Dictionary with screening statistics.
200+
"""
201+
total = self.n_screened + self.n_computed
202+
if total == 0:
203+
percent_screened = 0.0
204+
else:
205+
percent_screened = 100.0 * self.n_screened / total
206+
207+
return {
208+
"n_screened": self.n_screened,
209+
"n_computed": self.n_computed,
210+
"total": total,
211+
"percent_screened": percent_screened,
212+
"speedup_factor": total / max(self.n_computed, 1),
213+
}
214+
215+
def reset_counters(self):
216+
"""Reset screening counters."""
217+
self.n_screened = 0
218+
self.n_computed = 0

0 commit comments

Comments
 (0)