Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2d3fe98
feat: add Boys function APIs and validation tests
San1357 Feb 12, 2026
db5045e
fix: move qc-dev-env/ from.gitignore to .git/info/exclude
San1357 Feb 13, 2026
4824a2a
refactor: vectorize boys_function_all_orders with NumPy broadcasting
San1357 Feb 13, 2026
5278ebf
fix: add raise-from in mpmath ImportError (ruff B904)
San1357 Feb 26, 2026
c19cb89
feat: implement VRR (Eq. 65) for OS+HGP two-electron integral pipeline
San1357 Feb 26, 2026
2ec8140
feat: implement ETR (Eq. 66) and primitive contraction for OS+HGP pip…
San1357 Mar 3, 2026
6e40ec7
feat: implement HRR (Eq. 67) and complete OS+HGP pipeline
San1357 Mar 3, 2026
bf6e727
add test files for two elect int improved.py
San1357 Mar 3, 2026
8ab104e
test: add erf/erfc attenuated Boys function tests and update docstring
San1357 Mar 4, 2026
4347444
style: apply black/ruff formatting to test_boys_functions.py
San1357 Mar 4, 2026
d96fd22
style: apply black/ruff formatting fixes to test_two_elec_int_improve…
San1357 Mar 4, 2026
7ec98dd
style: move imports to top-level, fix ruff PLC0415 in test file
San1357 Mar 4, 2026
f0f57fa
style: fix black formatting
San1357 Mar 5, 2026
2cea131
refactor: vectorize norm computation over all 4 centers using NumPy a…
San1357 Mar 6, 2026
60ddebb
test: add tests for _optimized_contraction
San1357 Mar 6, 2026
e1612a7
fix: handle different K per center in _optimized_contraction
San1357 Mar 6, 2026
3411d23
style: fix black formatting and import sorting in test_electron_repul…
San1357 Mar 6, 2026
6314d78
feat: implement Schwarz screening to optimize two-electron integral c…
San1357 Mar 8, 2026
cdf9bd4
feat: add PySCF comparison tests and OS+HGP tutorial notebook
San1357 Mar 8, 2026
84c7511
Merge branch 'feat/VRR-two-electron-integral' into local-test-all
San1357 Mar 8, 2026
4ee3247
merge: resolve add/add conflicts keeping electron-repulsion-improved …
San1357 Mar 8, 2026
b6ff02c
Merge branch 'feat/schwarz-screening' into local-test-all
San1357 Mar 8, 2026
7c85807
Merge branch 'feat/pyscf-tests-tutorial' into local-test-all
San1357 Mar 8, 2026
f630272
fix: tighten libcint ERI tolerance and use improved implementation (#…
San1357 Mar 8, 2026
4e2994e
Merge branch 'feat/libcint-tolerance-fix' into local-test-all
San1357 Mar 8, 2026
b351377
fix: add pyscf to dev deps and remove conditional imports in test
San1357 Mar 11, 2026
6e3a73e
fix: use pytest.importorskip for pyscf instead of dev dependency
San1357 Mar 11, 2026
0c215e6
fix: pass rho=harm_mean to boys_func to support erf/erfc attenuated p…
San1357 Mar 12, 2026
874c15b
fix: replace tautological assertions with monotonicity check in test_…
San1357 Mar 12, 2026
65879ad
fix: pass screener via kwargs instead of class-level state for thread…
San1357 Mar 12, 2026
fb9e1a6
perf: cache SchwarzScreener to avoid recomputing bounds on every call
San1357 Mar 12, 2026
bfac8b0
revert: restore original ElectronRepulsionIntegral to use old algorithm
San1357 Mar 13, 2026
f2f0e0d
fix: use get_boys_function from boys_functions module in ElectronRepu…
San1357 Mar 13, 2026
76107ea
fix: use get_boys_function directly instead of ElectronRepulsionInteg…
San1357 Mar 13, 2026
104446c
fix: use boys_functions module directly instead of inline hyp1f1 or c…
San1357 Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 218 additions & 0 deletions gbasis/integrals/_schwarz_screening.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"""Integral screening utilities for efficient 2-electron integral computation.

This module implements Schwarz screening and shell-pair screening to skip
negligible integrals, providing speedup for spatially extended systems.

References:
- Häser, M. & Ahlrichs, R. J. Comput. Chem. 1989, 10, 104.
- Gill, P. M. W.; Johnson, B. G.; Pople, J. A. Int. J. Quantum Chem. 1991, 40, 745.
"""

import numpy as np


def compute_schwarz_bound_shell_pair(boys_func, cont_one, cont_two, compute_integral_func):
"""Compute Schwarz bound for a shell pair: sqrt((ab|ab)).

Parameters
----------
boys_func : callable
Boys function for integral evaluation.
cont_one : GeneralizedContractionShell
First contracted shell.
cont_two : GeneralizedContractionShell
Second contracted shell.
compute_integral_func : callable
Function to compute (ab|cd) integrals.

Returns
-------
bound : float
Schwarz bound sqrt(max|(ab|ab)|) for this shell pair.
"""
# Compute (ab|ab) integral
integral = compute_integral_func(
boys_func,
cont_one.coord,
cont_one.angmom,
cont_one.angmom_components_cart,
cont_one.exps,
cont_one.coeffs,
cont_two.coord,
cont_two.angmom,
cont_two.angmom_components_cart,
cont_two.exps,
cont_two.coeffs,
cont_one.coord,
cont_one.angmom,
cont_one.angmom_components_cart,
cont_one.exps,
cont_one.coeffs,
cont_two.coord,
cont_two.angmom,
cont_two.angmom_components_cart,
cont_two.exps,
cont_two.coeffs,
)

# Return sqrt of maximum absolute value
return np.sqrt(np.max(np.abs(integral)))


def compute_schwarz_bounds(contractions, boys_func, compute_integral_func):
"""Precompute Schwarz bounds for all shell pairs.

Parameters
----------
contractions : list of GeneralizedContractionShell
List of all contracted shells.
boys_func : callable
Boys function for integral evaluation.
compute_integral_func : callable
Function to compute (ab|cd) integrals.

Returns
-------
bounds : np.ndarray(n_shells, n_shells)
Schwarz bounds sqrt((ab|ab)) for each shell pair.
"""
n_shells = len(contractions)
bounds = np.zeros((n_shells, n_shells))

for i, cont_i in enumerate(contractions):
for j in range(i, n_shells):
cont_j = contractions[j]
bounds[i, j] = compute_schwarz_bound_shell_pair(
boys_func, cont_i, cont_j, compute_integral_func
)
bounds[j, i] = bounds[i, j] # Symmetry: (ab|ab) = (ba|ba)

return bounds


def shell_pair_significant(cont_one, cont_two, threshold=1e-12):
"""Check if a shell pair is significant using primitive screening.

Uses the Gaussian product theorem: exp(-a*b/(a+b) * |A-B|^2) factor.
If this factor is below threshold for all primitive pairs, skip.

Parameters
----------
cont_one : GeneralizedContractionShell
First contracted shell.
cont_two : GeneralizedContractionShell
Second contracted shell.
threshold : float
Screening threshold.

Returns
-------
significant : bool
True if shell pair might contribute significantly.
"""
# Distance between shell centers
r_ab_sq = np.sum((cont_one.coord - cont_two.coord) ** 2)

if r_ab_sq < 1e-10:
# Same center, always significant
return True

# Check if any primitive pair survives screening
for exp_a in cont_one.exps:
for exp_b in cont_two.exps:
# Gaussian decay factor
decay = np.exp(-exp_a * exp_b / (exp_a + exp_b) * r_ab_sq)
if decay > threshold:
return True

return False


class SchwarzScreener:
"""Class for Schwarz integral screening.

Precomputes Schwarz bounds and provides efficient screening.

Attributes
----------
bounds : np.ndarray
Schwarz bounds for all shell pairs.
threshold : float
Screening threshold.
n_screened : int
Counter for number of screened shell quartets.
n_computed : int
Counter for number of computed shell quartets.
"""

def __init__(self, contractions, boys_func, compute_integral_func, threshold=1e-12):
"""Initialize Schwarz screener.

Parameters
----------
contractions : list of GeneralizedContractionShell
List of all contracted shells.
boys_func : callable
Boys function for integral evaluation.
compute_integral_func : callable
Function to compute (ab|cd) integrals.
threshold : float
Screening threshold (default: 1e-12).
"""
self.threshold = threshold
self.n_screened = 0
self.n_computed = 0

# Precompute Schwarz bounds
self.bounds = compute_schwarz_bounds(contractions, boys_func, compute_integral_func)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, compute_schwarz_bounds calls compute_schwarz_bound_shell_pair for each pair of shells. Then compute_schwarz_bound_shell_pair computes the integrals for each shell pair. Isn't this the same as computing all the integrals unscreened?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I get this, you run a $O(n^2)$ screening to screen the $O(n^4)$ integrals.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, @marco-2023 — compute_schwarz_bounds runs in O(n²) over shell pairs, computing (ab|ab) type integrals. This upfront cost then screens out negligible shell quartets from the O(n⁴) ERI computation, which is where the real savings come from for larger basis sets.


def is_significant(self, i, j, k, l_shell):
"""Check if shell quartet (ij|kl) is significant.

Uses Schwarz inequality: |(ij|kl)| <= sqrt((ij|ij)) * sqrt((kl|kl))

Parameters
----------
i, j, k, l_shell : int
Shell indices.

Returns
-------
significant : bool
True if integral might be significant, False if can be skipped.
"""
bound = self.bounds[i, j] * self.bounds[k, l_shell]

if bound < self.threshold:
self.n_screened += 1
return False
else:
self.n_computed += 1
return True

def get_statistics(self):
"""Get screening statistics.

Returns
-------
stats : dict
Dictionary with screening statistics.
"""
total = self.n_screened + self.n_computed
if total == 0:
percent_screened = 0.0
else:
percent_screened = 100.0 * self.n_screened / total

return {
"n_screened": self.n_screened,
"n_computed": self.n_computed,
"total": total,
"percent_screened": percent_screened,
"speedup_factor": total / max(self.n_computed, 1),
}

def reset_counters(self):
"""Reset screening counters."""
self.n_screened = 0
self.n_computed = 0
Loading
Loading