fastesp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastesp-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zidong Shu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fastesp-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastesp
3
+ Version: 0.1.0
4
+ Summary: Exact electrostatic potential from a Gaussian-basis wavefunction, evaluated analytically and solved on the GPU
5
+ Author: Zidong Shu
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ZidongS/fastesp
8
+ Project-URL: Repository, https://github.com/ZidongS/fastesp
9
+ Project-URL: Issues, https://github.com/ZidongS/fastesp/issues
10
+ Keywords: computational-chemistry,electrostatic-potential,ESP,wavefunction,gaussian-basis,cuda,gpu,multiwfn,orca,molden
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: C++
16
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
17
+ Classifier: Topic :: Scientific/Engineering :: Physics
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy>=1.22
22
+ Requires-Dist: scipy>=1.8
23
+ Provides-Extra: test
24
+ Requires-Dist: pytest>=7; extra == "test"
25
+ Provides-Extra: bench
26
+ Requires-Dist: matplotlib>=3.5; extra == "bench"
27
+ Dynamic: license-file
28
+
29
+ # fastesp
30
+
31
+ **Exact electrostatic potential from a Gaussian-basis wavefunction — evaluated
32
+ analytically, and solved on the GPU.**
33
+
34
+ `fastesp` computes
35
+
36
+ ```
37
+ V(C) = sum_A Z_A/|C - R_A| - sum_{mu nu} P_{mu nu} < mu | 1/|r - C| | nu >
38
+ ```
39
+
40
+ at an arbitrary set of points, without any real-space quadrature. The one-electron
41
+ Coulomb integrals are evaluated in closed form (Hermite / McMurchie–Davidson
42
+ expansion of the Gaussian product, Boys functions for the 1/r operator), so the
43
+ result is exact to machine precision. It is a drop-in faster and more accurate
44
+ replacement for the "electrostatic potential at a set of points" function of
45
+ Multiwfn (menu `5 → 12 → 100`).
46
+
47
+ ## Benchmark
48
+
49
+ 54-atom protein fragment, ORCA wB97M-D3BJ/def2-TZVPPD (1450 spherical / 1634
50
+ Cartesian functions), points on the union of the Bondi vdW spheres. Multiwfn
51
+ 8 CPU threads vs one RTX 5090, measured with `benchmarks/benchmark.py` under
52
+ SLURM on a dedicated node.
53
+
54
+ | points | Multiwfn 1 thr | Multiwfn 8 thr | fastesp (1 GPU) | speedup |
55
+ |---|---|---|---|---|
56
+ | 300 | 206.07 s | 26.29 s | 0.269 s | **98x** |
57
+ | 3000 | 2061 s\* | 258.57 s | 0.479 s | **539x** |
58
+ | 30000 | 20607 s\* | 2586 s\* | 2.456 s | **>1000x** |
59
+
60
+ \* extrapolated; Multiwfn is linear in the point count at 86.2–87.6 ms/point.
61
+ The part that replaces Multiwfn's integration is **0.030 ms/point vs 87.6** —
62
+ a **2900x** speedup — because the table build is point-independent.
63
+
64
+ Accuracy at the identical 300 points:
65
+
66
+ | | |
67
+ |---|---|
68
+ | max abs difference vs Multiwfn | 1.75e-7 a.u. = **4.8e-3 mV** |
69
+ | max abs difference vs unscreened analytic evaluation | 1.3e-11 a.u. |
70
+ | primitive integrals vs independent quadrature | 6.6e-14 (relative) |
71
+ | electron density vs Multiwfn at the same points | 4.3e-12 (9e-10 relative) |
72
+
73
+ The 1.75e-7 a.u. gap is **Multiwfn's** quadrature error, and this is pinned down
74
+ by three measurements: (1) the electron density — a pure basis evaluation on
75
+ Multiwfn's side — agrees to 9e-10 relative, so both programs read the
76
+ wavefunction identically; (2) Multiwfn's answer is bit-for-bit unchanged when
77
+ its Becke grid is swept from 75×434 (1.8e6 points) to 200×2354 (2.5e7 points);
78
+ (3) its three internal ESP engines (`iESPcode` 1/2/3) agree exactly with each
79
+ other. See `benchmarks/check_density_mwfn.py` and
80
+ `benchmarks/mwfn_grid_convergence.py`.
81
+
82
+ ## Install
83
+
84
+ ```bash
85
+ pip install fastesp # NumPy reference (exact, no toolchain needed)
86
+ fastesp build-native # optional: compile the C++/CUDA backends
87
+ fastesp selftest # validate the analytic integrals
88
+ ```
89
+
90
+ The native backends are compiled into `~/.cache/fastesp` (override with
91
+ `FASTESP_CACHE`) and need `g++` (with OpenMP) and, for the GPU path, `nvcc`.
92
+ `pip install` alone already gives exact results — just slower.
93
+
94
+ ## Use
95
+
96
+ ```bash
97
+ # ESP at the points in pts.txt (Bohr, one "x y z" per line)
98
+ fastesp esp mol.molden.input --points pts.txt -o esp.txt
99
+ fastesp esp mol.molden.input --points pts.txt --backend numpy # force reference
100
+ ```
101
+
102
+ ```
103
+ backend : cuda
104
+ basis : 1634 Cartesian functions, 2080 primitives
105
+ pair table : 0.2600 s (point-independent)
106
+ ESP kernel : 0.0089 s
107
+ ESP range : -2.2055 .. +1.9854 V
108
+ ```
109
+
110
+ As a library:
111
+
112
+ ```python
113
+ import numpy as np
114
+ from fastesp import read_molden, Basis, build_table
115
+
116
+ wfn = read_molden("mol.molden.input")
117
+ basis = Basis(wfn)
118
+ dens = basis.density_matrix_cartesian()
119
+
120
+ table = build_table(wfn, basis, dens, points_bohr) # point-independent tables
121
+ esp = table.esp() # Hartree/e
122
+ ```
123
+
124
+ `examples/vdw_surface_esp.py` shows the full production workflow: sample the
125
+ Bondi vdW surface of a molecule and evaluate the ESP on it.
126
+
127
+ ## How it works
128
+
129
+ For a pair of primitive Cartesian Gaussians, the product is one Gaussian times a
130
+ polynomial. Expanding about the Gaussian product centre `P` in Hermite Gaussians
131
+ `L_t` gives the exact closed form
132
+
133
+ ```
134
+ g_A^a g_B^b = K sum_t E_t L_t(r - P)
135
+ int L_t(r')/|R - r'| dr' = (-1)^|t| (d/dR)^t F(R), F(R) = (2 pi / p) F_0(p R^2)
136
+ ```
137
+
138
+ and `(d/dR)^t F` is expanded exactly by the one-dimensional recurrence
139
+ `a[t+1][j] = -a[t][j] + (t-2j+2) a[t][j-1]`.
140
+
141
+ The structural step that makes this fast: **everything except `R = C - P` is
142
+ independent of the sampling point.** Contracting the E-coefficients, the `(-1)^|t|`
143
+ signs and the `(2p)^m` factors over the Cartesian components and over the
144
+ primitive pairs — weighted by the density matrix — yields, once per primitive
145
+ pair, a sparse table `G[alpha, m]` such that
146
+
147
+ ```
148
+ I_pair(C) = K * sum_{alpha,m} G[alpha,m] * R^alpha * F_m(p R^2)
149
+ ```
150
+
151
+ Per point that is one Boys evaluation, a short monomial schedule and one dot
152
+ product — a shape that maps directly onto GPU threads. The `O(N_pairs)` table
153
+ build is independent of the point set (and of how many points there are), so it
154
+ is done once and reused; only the `O(N_pairs x N_points)` evaluation runs per
155
+ point set.
156
+
157
+ Screening uses the rigorous bound `|I_pair| <= K * int|rho_pair| / d`, with
158
+ `int|rho_pair|` obtained from the `|He_n|` moment products computed at build time.
159
+ The builder also prints the sum rule `Tr(P S) = N_elec` as a sanity check on the
160
+ basis it just read.
161
+
162
+ ### MOLDEN conventions
163
+
164
+ The MOLDEN format does not pin these down. All three were determined from the
165
+ file itself and are verified by `Tr(P S) = N_elec` and `max|C^T S C - I|`:
166
+
167
+ 1. **Primitive normalisation.** ORCA writes GTO coefficients that already
168
+ include `N_s = (2 alpha/pi)^{3/4} (4 alpha)^{l/2}` — the radial prefactor
169
+ *without* the angular `(2l-1)!!` factor of a fully normalised Cartesian
170
+ Gaussian. Verified against every single-primitive p/d/f shell in the test
171
+ file to 8 digits.
172
+ 2. **Angular ordering.** The spherical functions are the real solid harmonics in
173
+ MOLDEN order `m = 0, +1, -1, +2, -2, ...` (`fastesp/basis.py`, `_POLY`).
174
+ 3. **Cartesian ↔ spherical transform.** Rows are S-orthonormalised so that
175
+ `T S T^T = I`.
176
+
177
+ If you have a wavefunction whose conventions differ, `Basis` +
178
+ `overlap_matrix` will show it: `Tr(P S)` will not reproduce the MOLDEN
179
+ occupancy sum.
180
+
181
+ ## Repository layout
182
+
183
+ | path | role |
184
+ |---|---|
185
+ | `fastesp/molden.py` | MOLDEN reader |
186
+ | `fastesp/basis.py` | Cartesian basis, conventions, density matrix |
187
+ | `fastesp/ints.py` | exact primitive-pair Coulomb integrals |
188
+ | `fastesp/esp.py` | contracted pair tables + NumPy ESP |
189
+ | `fastesp/table.py` | table construction, binary I/O, evaluation |
190
+ | `fastesp/cli.py` | `fastesp` console script |
191
+ | `fastesp/native/build_pairs.cpp` | OpenMP table builder (fast path) |
192
+ | `fastesp/native/esp_gpu.cu` | CUDA solver |
193
+ | `tests/` | integral and end-to-end validation |
194
+ | `benchmarks/` | head-to-head benchmark and cross-checks |
195
+
196
+ ## Limitations
197
+
198
+ * Only the vacuum wavefunction ESP is implemented. Adding external point charges
199
+ (e.g. a CPCM reaction field) is a one-line addition to the nuclear term.
200
+ * `l = 4` (g) solid harmonics are tabulated but unverified — the test file has no
201
+ g functions.
202
+ * The GPU solver is fp64 throughout; there is headroom for a mixed-precision
203
+ build if needed.
204
+ * Tested against ORCA-generated MOLDEN files; other writers may use different
205
+ conventions (see above).
206
+
207
+ ## Citation
208
+
209
+ If you use this in published work, please cite the repository
210
+ (https://github.com/ZidongS/fastesp).
211
+
212
+ ## License
213
+
214
+ MIT — see `LICENSE`.
@@ -0,0 +1,186 @@
1
+ # fastesp
2
+
3
+ **Exact electrostatic potential from a Gaussian-basis wavefunction — evaluated
4
+ analytically, and solved on the GPU.**
5
+
6
+ `fastesp` computes
7
+
8
+ ```
9
+ V(C) = sum_A Z_A/|C - R_A| - sum_{mu nu} P_{mu nu} < mu | 1/|r - C| | nu >
10
+ ```
11
+
12
+ at an arbitrary set of points, without any real-space quadrature. The one-electron
13
+ Coulomb integrals are evaluated in closed form (Hermite / McMurchie–Davidson
14
+ expansion of the Gaussian product, Boys functions for the 1/r operator), so the
15
+ result is exact to machine precision. It is a drop-in faster and more accurate
16
+ replacement for the "electrostatic potential at a set of points" function of
17
+ Multiwfn (menu `5 → 12 → 100`).
18
+
19
+ ## Benchmark
20
+
21
+ 54-atom protein fragment, ORCA wB97M-D3BJ/def2-TZVPPD (1450 spherical / 1634
22
+ Cartesian functions), points on the union of the Bondi vdW spheres. Multiwfn
23
+ 8 CPU threads vs one RTX 5090, measured with `benchmarks/benchmark.py` under
24
+ SLURM on a dedicated node.
25
+
26
+ | points | Multiwfn 1 thr | Multiwfn 8 thr | fastesp (1 GPU) | speedup |
27
+ |---|---|---|---|---|
28
+ | 300 | 206.07 s | 26.29 s | 0.269 s | **98x** |
29
+ | 3000 | 2061 s\* | 258.57 s | 0.479 s | **539x** |
30
+ | 30000 | 20607 s\* | 2586 s\* | 2.456 s | **>1000x** |
31
+
32
+ \* extrapolated; Multiwfn is linear in the point count at 86.2–87.6 ms/point.
33
+ The part that replaces Multiwfn's integration is **0.030 ms/point vs 87.6** —
34
+ a **2900x** speedup — because the table build is point-independent.
35
+
36
+ Accuracy at the identical 300 points:
37
+
38
+ | | |
39
+ |---|---|
40
+ | max abs difference vs Multiwfn | 1.75e-7 a.u. = **4.8e-3 mV** |
41
+ | max abs difference vs unscreened analytic evaluation | 1.3e-11 a.u. |
42
+ | primitive integrals vs independent quadrature | 6.6e-14 (relative) |
43
+ | electron density vs Multiwfn at the same points | 4.3e-12 (9e-10 relative) |
44
+
45
+ The 1.75e-7 a.u. gap is **Multiwfn's** quadrature error, and this is pinned down
46
+ by three measurements: (1) the electron density — a pure basis evaluation on
47
+ Multiwfn's side — agrees to 9e-10 relative, so both programs read the
48
+ wavefunction identically; (2) Multiwfn's answer is bit-for-bit unchanged when
49
+ its Becke grid is swept from 75×434 (1.8e6 points) to 200×2354 (2.5e7 points);
50
+ (3) its three internal ESP engines (`iESPcode` 1/2/3) agree exactly with each
51
+ other. See `benchmarks/check_density_mwfn.py` and
52
+ `benchmarks/mwfn_grid_convergence.py`.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install fastesp # NumPy reference (exact, no toolchain needed)
58
+ fastesp build-native # optional: compile the C++/CUDA backends
59
+ fastesp selftest # validate the analytic integrals
60
+ ```
61
+
62
+ The native backends are compiled into `~/.cache/fastesp` (override with
63
+ `FASTESP_CACHE`) and need `g++` (with OpenMP) and, for the GPU path, `nvcc`.
64
+ `pip install` alone already gives exact results — just slower.
65
+
66
+ ## Use
67
+
68
+ ```bash
69
+ # ESP at the points in pts.txt (Bohr, one "x y z" per line)
70
+ fastesp esp mol.molden.input --points pts.txt -o esp.txt
71
+ fastesp esp mol.molden.input --points pts.txt --backend numpy # force reference
72
+ ```
73
+
74
+ ```
75
+ backend : cuda
76
+ basis : 1634 Cartesian functions, 2080 primitives
77
+ pair table : 0.2600 s (point-independent)
78
+ ESP kernel : 0.0089 s
79
+ ESP range : -2.2055 .. +1.9854 V
80
+ ```
81
+
82
+ As a library:
83
+
84
+ ```python
85
+ import numpy as np
86
+ from fastesp import read_molden, Basis, build_table
87
+
88
+ wfn = read_molden("mol.molden.input")
89
+ basis = Basis(wfn)
90
+ dens = basis.density_matrix_cartesian()
91
+
92
+ table = build_table(wfn, basis, dens, points_bohr) # point-independent tables
93
+ esp = table.esp() # Hartree/e
94
+ ```
95
+
96
+ `examples/vdw_surface_esp.py` shows the full production workflow: sample the
97
+ Bondi vdW surface of a molecule and evaluate the ESP on it.
98
+
99
+ ## How it works
100
+
101
+ For a pair of primitive Cartesian Gaussians, the product is one Gaussian times a
102
+ polynomial. Expanding about the Gaussian product centre `P` in Hermite Gaussians
103
+ `L_t` gives the exact closed form
104
+
105
+ ```
106
+ g_A^a g_B^b = K sum_t E_t L_t(r - P)
107
+ int L_t(r')/|R - r'| dr' = (-1)^|t| (d/dR)^t F(R), F(R) = (2 pi / p) F_0(p R^2)
108
+ ```
109
+
110
+ and `(d/dR)^t F` is expanded exactly by the one-dimensional recurrence
111
+ `a[t+1][j] = -a[t][j] + (t-2j+2) a[t][j-1]`.
112
+
113
+ The structural step that makes this fast: **everything except `R = C - P` is
114
+ independent of the sampling point.** Contracting the E-coefficients, the `(-1)^|t|`
115
+ signs and the `(2p)^m` factors over the Cartesian components and over the
116
+ primitive pairs — weighted by the density matrix — yields, once per primitive
117
+ pair, a sparse table `G[alpha, m]` such that
118
+
119
+ ```
120
+ I_pair(C) = K * sum_{alpha,m} G[alpha,m] * R^alpha * F_m(p R^2)
121
+ ```
122
+
123
+ Per point that is one Boys evaluation, a short monomial schedule and one dot
124
+ product — a shape that maps directly onto GPU threads. The `O(N_pairs)` table
125
+ build is independent of the point set (and of how many points there are), so it
126
+ is done once and reused; only the `O(N_pairs x N_points)` evaluation runs per
127
+ point set.
128
+
129
+ Screening uses the rigorous bound `|I_pair| <= K * int|rho_pair| / d`, with
130
+ `int|rho_pair|` obtained from the `|He_n|` moment products computed at build time.
131
+ The builder also prints the sum rule `Tr(P S) = N_elec` as a sanity check on the
132
+ basis it just read.
133
+
134
+ ### MOLDEN conventions
135
+
136
+ The MOLDEN format does not pin these down. All three were determined from the
137
+ file itself and are verified by `Tr(P S) = N_elec` and `max|C^T S C - I|`:
138
+
139
+ 1. **Primitive normalisation.** ORCA writes GTO coefficients that already
140
+ include `N_s = (2 alpha/pi)^{3/4} (4 alpha)^{l/2}` — the radial prefactor
141
+ *without* the angular `(2l-1)!!` factor of a fully normalised Cartesian
142
+ Gaussian. Verified against every single-primitive p/d/f shell in the test
143
+ file to 8 digits.
144
+ 2. **Angular ordering.** The spherical functions are the real solid harmonics in
145
+ MOLDEN order `m = 0, +1, -1, +2, -2, ...` (`fastesp/basis.py`, `_POLY`).
146
+ 3. **Cartesian ↔ spherical transform.** Rows are S-orthonormalised so that
147
+ `T S T^T = I`.
148
+
149
+ If you have a wavefunction whose conventions differ, `Basis` +
150
+ `overlap_matrix` will show it: `Tr(P S)` will not reproduce the MOLDEN
151
+ occupancy sum.
152
+
153
+ ## Repository layout
154
+
155
+ | path | role |
156
+ |---|---|
157
+ | `fastesp/molden.py` | MOLDEN reader |
158
+ | `fastesp/basis.py` | Cartesian basis, conventions, density matrix |
159
+ | `fastesp/ints.py` | exact primitive-pair Coulomb integrals |
160
+ | `fastesp/esp.py` | contracted pair tables + NumPy ESP |
161
+ | `fastesp/table.py` | table construction, binary I/O, evaluation |
162
+ | `fastesp/cli.py` | `fastesp` console script |
163
+ | `fastesp/native/build_pairs.cpp` | OpenMP table builder (fast path) |
164
+ | `fastesp/native/esp_gpu.cu` | CUDA solver |
165
+ | `tests/` | integral and end-to-end validation |
166
+ | `benchmarks/` | head-to-head benchmark and cross-checks |
167
+
168
+ ## Limitations
169
+
170
+ * Only the vacuum wavefunction ESP is implemented. Adding external point charges
171
+ (e.g. a CPCM reaction field) is a one-line addition to the nuclear term.
172
+ * `l = 4` (g) solid harmonics are tabulated but unverified — the test file has no
173
+ g functions.
174
+ * The GPU solver is fp64 throughout; there is headroom for a mixed-precision
175
+ build if needed.
176
+ * Tested against ORCA-generated MOLDEN files; other writers may use different
177
+ conventions (see above).
178
+
179
+ ## Citation
180
+
181
+ If you use this in published work, please cite the repository
182
+ (https://github.com/ZidongS/fastesp).
183
+
184
+ ## License
185
+
186
+ MIT — see `LICENSE`.
@@ -0,0 +1,42 @@
1
+ """fastesp -- exact electrostatic potential from a Gaussian-basis wavefunction.
2
+
3
+ The electron part of the ESP at a point C is
4
+
5
+ V_el(C) = -sum_{mu nu} P_{mu nu} < mu | 1/|r - C| | nu >
6
+
7
+ evaluated *analytically*: the Gaussian product is expanded in Hermite Gaussians
8
+ (McMurchie-Davidson) and the potential of each Hermite Gaussian is a closed-form
9
+ derivative of the Boys function. No real-space quadrature is involved, so the
10
+ result is exact to machine precision.
11
+
12
+ Quick start::
13
+
14
+ from fastesp import read_molden, Basis, build_table
15
+
16
+ wfn = read_molden("mol.molden.input")
17
+ basis = Basis(wfn)
18
+ density = basis.density_matrix_cartesian()
19
+ table = build_table(wfn, basis, density, points_bohr)
20
+ esp = table.esp() # (n_points,) in Hartree/e
21
+
22
+ See :mod:`fastesp.cli` (console script ``fastesp``) for the compiled C++/CUDA
23
+ backends, which are orders of magnitude faster on large point sets.
24
+ """
25
+ from __future__ import annotations
26
+
27
+ from .basis import Basis, cart_order, overlap_matrix
28
+ from .esp import ESPEngine
29
+ from .ints import boys, primitive_pair_integral
30
+ from .molden import Wavefunction, read_molden
31
+ from .table import (PairTable, basis_payload, build_table, write_basis_payload)
32
+
33
+ __version__ = "0.1.0"
34
+
35
+ __all__ = [
36
+ "Wavefunction", "read_molden",
37
+ "Basis", "cart_order", "overlap_matrix",
38
+ "boys", "primitive_pair_integral",
39
+ "PairTable", "build_table",
40
+ "basis_payload", "write_basis_payload",
41
+ "__version__",
42
+ ]