fastesp
Exact electrostatic potential from a Gaussian-basis wavefunction — evaluated analytically, and solved on the GPU.
fastesp computes
V(C) = sum_A Z_A/|C - R_A| - sum_{mu nu} P_{mu nu} < mu | 1/|r - C| | nu >
at an arbitrary set of points, without any real-space quadrature. The one-electron
Coulomb integrals are evaluated in closed form (Hermite / McMurchie–Davidson
expansion of the Gaussian product, Boys functions for the 1/r operator), so the
result is exact to machine precision. It is a drop-in faster and more accurate
replacement for the "electrostatic potential at a set of points" function of
Multiwfn (menu 5 → 12 → 100).
Benchmark
54-atom protein fragment, ORCA wB97M-D3BJ/def2-TZVPPD (1450 spherical / 1634
Cartesian functions), points on the union of the Bondi vdW spheres. Multiwfn
8 CPU threads vs one RTX 5090, measured with benchmarks/benchmark.py under
SLURM on a dedicated node.
| points | Multiwfn 1 thr | Multiwfn 8 thr | fastesp (1 GPU) | speedup |
|---|---|---|---|---|
| 300 | 206.07 s | 26.29 s | 0.269 s | 98x |
| 3000 | 2061 s* | 258.57 s | 0.479 s | 539x |
| 30000 | 20607 s* | 2586 s* | 2.456 s | >1000x |
* extrapolated; Multiwfn is linear in the point count at 86.2–87.6 ms/point. The part that replaces Multiwfn's integration is 0.030 ms/point vs 87.6 — a 2900x speedup — because the table build is point-independent.
Accuracy at the identical 300 points:
| max abs difference vs Multiwfn | 1.75e-7 a.u. = 4.8e-3 mV |
| max abs difference vs unscreened analytic evaluation | 1.3e-11 a.u. |
| primitive integrals vs independent quadrature | 6.6e-14 (relative) |
| electron density vs Multiwfn at the same points | 4.3e-12 (9e-10 relative) |
The 1.75e-7 a.u. gap is Multiwfn's quadrature error, and this is pinned down
by three measurements: (1) the electron density — a pure basis evaluation on
Multiwfn's side — agrees to 9e-10 relative, so both programs read the
wavefunction identically; (2) Multiwfn's answer is bit-for-bit unchanged when
its Becke grid is swept from 75×434 (1.8e6 points) to 200×2354 (2.5e7 points);
(3) its three internal ESP engines (iESPcode 1/2/3) agree exactly with each
other. See benchmarks/check_density_mwfn.py and
benchmarks/mwfn_grid_convergence.py.
Install
pip install fastesp # NumPy reference (exact, no toolchain needed)
fastesp build-native # optional: compile the C++/CUDA backends
fastesp selftest # validate the analytic integrals
The native backends are compiled into ~/.cache/fastesp (override with
FASTESP_CACHE) and need g++ (with OpenMP) and, for the GPU path, nvcc.
pip install alone already gives exact results — just slower.
Use
# ESP at the points in pts.txt (Bohr, one "x y z" per line)
fastesp esp mol.molden.input --points pts.txt -o esp.txt
fastesp esp mol.molden.input --points pts.txt --backend numpy # force reference
backend : cuda
basis : 1634 Cartesian functions, 2080 primitives
pair table : 0.2600 s (point-independent)
ESP kernel : 0.0089 s
ESP range : -2.2055 .. +1.9854 V
As a library:
import numpy as np
from fastesp import read_molden, Basis, build_table
wfn = read_molden("mol.molden.input")
basis = Basis(wfn)
dens = basis.density_matrix_cartesian()
table = build_table(wfn, basis, dens, points_bohr) # point-independent tables
esp = table.esp() # Hartree/e
examples/vdw_surface_esp.py shows the full production workflow: sample the
Bondi vdW surface of a molecule and evaluate the ESP on it.
How it works
For a pair of primitive Cartesian Gaussians, the product is one Gaussian times a
polynomial. Expanding about the Gaussian product centre P in Hermite Gaussians
L_t gives the exact closed form
g_A^a g_B^b = K sum_t E_t L_t(r - P)
int L_t(r')/|R - r'| dr' = (-1)^|t| (d/dR)^t F(R), F(R) = (2 pi / p) F_0(p R^2)
and (d/dR)^t F is expanded exactly by the one-dimensional recurrence
a[t+1][j] = -a[t][j] + (t-2j+2) a[t][j-1].
The structural step that makes this fast: everything except R = C - P is
independent of the sampling point. Contracting the E-coefficients, the (-1)^|t|
signs and the (2p)^m factors over the Cartesian components and over the
primitive pairs — weighted by the density matrix — yields, once per primitive
pair, a sparse table G[alpha, m] such that
I_pair(C) = K * sum_{alpha,m} G[alpha,m] * R^alpha * F_m(p R^2)
Per point that is one Boys evaluation, a short monomial schedule and one dot
product — a shape that maps directly onto GPU threads. The O(N_pairs) table
build is independent of the point set (and of how many points there are), so it
is done once and reused; only the O(N_pairs x N_points) evaluation runs per
point set.
Screening uses the rigorous bound |I_pair| <= K * int|rho_pair| / d, with
int|rho_pair| obtained from the |He_n| moment products computed at build time.
The builder also prints the sum rule Tr(P S) = N_elec as a sanity check on the
basis it just read.
MOLDEN conventions
The MOLDEN format does not pin these down. All three were determined from the
file itself and are verified by Tr(P S) = N_elec and max|C^T S C - I|:
- Primitive normalisation. ORCA writes GTO coefficients that already
include
N_s = (2 alpha/pi)^{3/4} (4 alpha)^{l/2}— the radial prefactor without the angular(2l-1)!!factor of a fully normalised Cartesian Gaussian. Verified against every single-primitive p/d/f shell in the test file to 8 digits. - Angular ordering. The spherical functions are the real solid harmonics in
MOLDEN order
m = 0, +1, -1, +2, -2, ...(fastesp/basis.py,_POLY). - Cartesian ↔ spherical transform. Rows are S-orthonormalised so that
T S T^T = I.
If you have a wavefunction whose conventions differ, Basis +
overlap_matrix will show it: Tr(P S) will not reproduce the MOLDEN
occupancy sum.
Repository layout
| path | role |
|---|---|
fastesp/molden.py |
MOLDEN reader |
fastesp/basis.py |
Cartesian basis, conventions, density matrix |
fastesp/ints.py |
exact primitive-pair Coulomb integrals |
fastesp/esp.py |
contracted pair tables + NumPy ESP |
fastesp/table.py |
table construction, binary I/O, evaluation |
fastesp/cli.py |
fastesp console script |
fastesp/native/build_pairs.cpp |
OpenMP table builder (fast path) |
fastesp/native/esp_gpu.cu |
CUDA solver |
tests/ |
integral and end-to-end validation |
benchmarks/ |
head-to-head benchmark and cross-checks |
Limitations
- Only the vacuum wavefunction ESP is implemented. Adding external point charges (e.g. a CPCM reaction field) is a one-line addition to the nuclear term.
l = 4(g) solid harmonics are tabulated but unverified — the test file has no g functions.- The GPU solver is fp64 throughout; there is headroom for a mixed-precision build if needed.
- Tested against ORCA-generated MOLDEN files; other writers may use different conventions (see above).
Citation
If you use this in published work, please cite the repository (https://github.com/ZidongS/fastesp).
License
MIT — see LICENSE.
Metadata
Release files for fastesp 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fastesp-0.1.0.tar.gz | 41.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fastesp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 80.3 kB
Release files / fastesp-0.1.0.tar.gz
| Download URL | fastesp-0.1.0.tar.gz |
|---|---|
| Size | 41.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
85ff055f4d9207c8483aad3120c462b8d5d4cc80924b848fb1b2f49cc960fdc9
|
|
BLAKE2b-256 checksum How to use checksums |
492ae83950a29faadc43dadbab1f9149056c0daac557f2838579190b4d89b4a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.9
|
Release files / fastesp-0.1.0-py3-none-any.whl
| Download URL | fastesp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 38.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f41de24670eb705add564d291c326fa31f3a71032d09ba232d833e749bb16291
|
|
BLAKE2b-256 checksum How to use checksums |
32ed7f956e502ab678d7fba467029c1f41a9e3760d38996d962787d7a0cd5b6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.9
|