A pure Python Gaussian basis DFT code with GPU acceleration for efficient quantum chemistry calculations
Explore the docs »
Homepage
·
Try the GUI
·
View Demo
·
Report Bug
·
Request Feature
Table of Contents
- About The Project
- Key Features
- Installation
- Quick Start
- Usage
- Computing Molecular Integrals
- GPU-Accelerated Integrals
- Cartesian <-> Spherical Basis
- Subset Evaluation
- Skala: the neural XC functional
- Initial Guess for the SCF
- XC Integration Grids
- Density-Fitting Coulomb Algorithms and Memory Budget
- Analytical Forces & Geometry Optimization
- Generating Visualization Files
- Graphical User Interface
- Tutorials
- Documentation
- Roadmap
- Contributing
- License
- Citation
- Contact
PyFock is a pure Python quantum chemistry package that enables efficient Kohn-Sham density functional theory (DFT) calculations for molecular systems. Unlike traditional quantum chemistry codes written in Fortran or C/C++, PyFock is written entirely in Python—including all performance-critical molecular integral evaluations—while achieving computational efficiency comparable to established codes like PySCF and Psi4.
- 100% Pure Python: All code, including computationally intensive molecular integrals, is written in Python
- High Performance: Achieves efficiency comparable to C/C++ backends through Numba JIT compilation, NumPy, NumExpr, SciPy, and CuPy
- GPU Acceleration: Leverages CUDA via Numba and CuPy for up to 14× speedup on large systems
- Easy Installation: Simple
pip installon all major operating systems (Linux, macOS, Windows) - Accessible: Designed for education, prototyping, and community development
- Near-Quadratic Scaling: ~O(N²·⁰⁵) scaling through density fitting with Cauchy-Schwarz screening
- Gaussian-Type Orbitals: Employs GTOs as basis functions for molecular calculations
- Efficient Parallelization: Multi-core CPU support and multi-GPU acceleration capabilities
- Numerical accuracy: Consistent with PySCF (< 10⁻⁷ Ha)
- Parallel efficiency: Comparable to state-of-the-art C++ backends on multicore CPUs
- GPU speedup: Up to 14× faster than 4-core CPU execution for large systems
- Scaling: Near-quadratic ~O(N²·⁰⁵) for electron repulsion integrals (Coulomb term)
- XC evaluation: Sub-quadratic scaling ~O(N¹·²⁵⁻¹·⁵) for exchange-correlation contributions
- ✅ Pure Python Implementation: Including molecular integral evaluations (overlap, kinetic, nuclear attraction, electron repulsion integrals)
- ✅ Density Fitting: Efficient density fitting approximation with Cauchy-Schwarz screening
- ✅ GPU Acceleration: Full GPU support for integral evaluation, XC term, and matrix operations.
use_gpu=Trueruns the XC term in single precision until the relative energy change drops below 5e-7 and in double precision from there on (dynamic_precision, on by default since it is ~1.5x faster for the same converged energy; automatically disabled for meta-GGAs, whose tau single precision resolves too poorly) - ✅ Multiple Integration Schemes:
- Classical Taketa-Huzinaga-O-ohata scheme
- Rys quadrature method (roots 1–10) for efficient ERI evaluation
- Obara-Saika method for ERI evaluation
- ✅ XC Functionals: Support for LDA, GGA and meta-GGA functionals natively and optionally via LibXC integration, plus the Skala neural functional
- ✅ DIIS Convergence: Direct inversion of iterative subspace for SCF acceleration
- ✅ Parallel Execution: Multi-core CPU and multi-GPU support via Numba and Joblib
- ✅ Modular Design: Standalone integral modules for benchmarking and embedding
- ✅ Web-based GUI: Interactive interface for visualization and input generation
- ✅ Cartesian and Spherical Basis: Support for both CAO and SAO representations
- ✅ Effective Core Potentials: Support for evaluation of ECP integrals
- ✅ Analytical gradients & forces: Fast analytical nuclear gradients for density-fitted DFT — one-electron (overlap/kinetic/nuclear), DF Coulomb (3c2e + 2c2e), and XC for LDA, GGA and meta-GGA (native or LibXC) — matching PySCF forces and faster
- ✅ ASE Calculator: Optional ASE interface (geometry optimization and the wider ASE ecosystem), using analytical forces by default
- ✅ XC integration grids: Treutler-Ahlrichs radial and Lebedev angular grids with region-wise angular pruning and Becke partitioning (levels 0-9), built by a parallel Numba kernel or on the GPU (~6x faster, same grid); energies agree with PySCF to the SCF convergence threshold in our benchmarks; numgrid grids remain available as an alternative scheme
- ✅ SANO initial guess: superposition of atomic natural-orbital densities (ANO-RCC-MB natural orbitals projected onto the calculation basis, no atomic SCF needed) as the default SCF starting point; it roughly halves the number of SCF iterations relative to the core-Hamiltonian guess and prunes the XC grid accurately
- ✅ Cross-Platform: Works on Linux, macOS, and Windows
PyFock can be easily installed via pip:
pip install pyfockTo get the latest development version directly from GitHub:
pip install git+https://github.com/manassharma07/pyfock.gitOr clone the repository and install locally:
git clone https://github.com/manassharma07/pyfock.git
cd pyfock
pip install -e .PyFock can use LibXC for exchange-correlation functionals not available natively in PyFock. The installation method depends on your system:
conda install -c conda-forge pylibxc -ysudo apt-get install libxc-dev
pip install pylibxc2brew install libxc
pip install pylibxc2Note: The conda method is recommended as it works reliably across all platforms.
None of these are needed to import or run PyFock. Each is available as a pip extra:
| Extra | Installs | Needed for |
|---|---|---|
pyfock[ase] |
ase |
the PyFockCalculator ASE interface: geometry optimization, NEB, MD |
pyfock[dispersion] |
dftd3 |
DFT-D3 corrections, DFT(..., dispersion=...) |
pyfock[dispersion-gpu] |
torch-dftd |
evaluating D3 on a GPU, through the ASE calculator |
pyfock[skala] |
torch, huggingface_hub, dftd3 |
the Skala neural functional |
Extras combine as usual:
pip install "pyfock[ase,skala]"and work the same when installing from a clone:
pip install -e ".[ase,skala]"GPU acceleration is kept separate, because the right wheel depends on your CUDA version:
pip install cupy-cuda11x # Replace 11x with your CUDA versionTwo notes:
- ASE is only required when you actually use
PyFockCalculator; PyFock imports and runs without it. pyfock[skala]does not install theskalapackage. PyFock reads the published TorchScript checkpoint directly withtorch.jit.load, which avoids that package's own dependencies on PySCF (no Windows wheels) and e3nn.dftd3is included because Skala is parametrised together with a DFT-D3 correction, and it is what reproduces Skala's published numbers. See Skala: the neural exchange-correlation functional.
Here's a minimal example to get you started with PyFock:
from pyfock import Basis, Mol, DFT
# Define molecule from XYZ file
mol = Mol(coordfile='h2o.xyz')
# Set up basis sets
basis = Basis(mol, {'all': Basis.load(mol=mol, basis_name='def2-SVP')})
auxbasis = Basis(mol, {'all': Basis.load(mol=mol, basis_name='def2-universal-jfit')})
# Create DFT calculation object
dftObj = DFT(mol, basis, auxbasis, xc='PBE')
# Set calculation parameters
dftObj.conv_crit = 1e-7
dftObj.max_itr = 20
dftObj.ncores = 4
# Run SCF calculation
energy, dmat = dftObj.scf()
print(f"Total Energy: {energy} Ha")xc also accepts LibXC IDs instead of a name — xc=[101, 130] is the same PBE as xc='PBE'.
PyFock provides standalone access to all molecular integrals:
from pyfock import Integrals, Basis, Mol
mol = Mol(coordfile='h2o.xyz')
basis = Basis(mol, {'all': Basis.load(mol=mol, basis_name='def2-SVP')})
# One-electron integrals
S_ovlp = Integrals.overlap_mat_symm(basis)
V_kin = Integrals.kin_mat_symm(basis)
V_nuc = Integrals.nuc_mat_symm(basis, mol)
# Two-electron integrals (classical scheme)
ERI_slow = Integrals.conv_4c2e_symm(basis)
# Two-electron integrals (Rys quadrature - faster)
ERI_fast = Integrals.rys_4c2e_symm(basis)
# Three-center integrals for density fitting
ERI_3c2e = Integrals.rys_3c2e_symm(basis, auxbasis)
# Two-center integrals
ERI_2c2e = Integrals.rys_2c2e_symm(basis)# GPU versions (returns CuPy arrays in device memory)
S_ovlp_gpu = Integrals.overlap_mat_symm_cupy(basis)
V_kin_gpu = Integrals.kin_mat_symm_cupy(basis)
V_nuc_gpu = Integrals.nuc_mat_symm_cupy(basis, mol)
ERI_3c2e_gpu = Integrals.rys_3c2e_symm_cupy(basis, auxbasis)# Convert from Cartesian to Spherical atomic orbitals
V_kin_CAO = Integrals.kin_mat_symm(basis)
c2sph_mat = basis.cart2sph_basis()
V_kin_SAO = np.dot(c2sph_mat, np.dot(V_kin_CAO, c2sph_mat.T))# Evaluate integrals for a subset of basis functions
S_ovlp_subset = Integrals.overlap_mat_symm(basis, slice=[0, 5, 0, 5])
# slice = [row_start, row_end, col_start, col_end]Skala is a machine-learned exchange-correlation functional from
Microsoft Research AI for Science that reaches hybrid-like accuracy at semilocal cost. Pass its name as
the xc argument and nothing else changes:
from pyfock import Basis, DFT, Grids, Mol
mol = Mol(coordfile='H2O.xyz')
basis = Basis(mol, {'all': Basis.load(mol=mol, basis_name='def2-SVP')})
auxbasis = Basis(mol, {'all': Basis.load(mol=mol, basis_name='def2-universal-jfit')})
dftObj = DFT(mol, basis, auxbasis, xc='skala-1.1', grids=Grids(mol, level=3))
dftObj.conv_crit = 1e-8
energy, dmat = dftObj.scf()Available names are skala-1.1 (recommended), skala-1.1-rev1, skala-1.1-rev0 and skala-1.0.
See examples/ex44_Skala_neural_functional.py for a runnable
version, with and without the dispersion correction.
Two Kaggle notebooks walk through the integration and can be run in the browser: Skala in PyFock vs. PySCF runs Skala on the CPU, checks PyFock's results against PySCF, compares the timings (PyFock is considerably faster) and finishes with a geometry optimization; PyFock + Skala on a Tesla T4 shows how to run Skala on the GPU and benchmarks it.
pip install "pyfock[skala]" is all that is needed (see
Optional Dependencies) — notably not the skala package itself, which is
why this works on Windows despite Skala's own packaging being Linux/macOS only.
The 2.4 MB checkpoint is downloaded from Hugging Face on first use and cached afterwards. Its SHA-256 is
verified against the digests published by Microsoft before it is loaded, because TorchScript
deserialization executes code from the file. To run without network access — or without
huggingface_hub at all — download skala-1.1-rev1.fun from
huggingface.co/microsoft/skala-1.1 and set:
export SKALA_LOCAL_MODEL_PATH=/path/to/skala-1.1-rev1.funNote that this path bypasses hash verification, so only point it at a file you trust.
Every other functional in PyFock is pointwise: the energy density at a grid point depends only on the
density at that point. Skala is not — its non-local layers aggregate over the points of each atomic grid,
so it consumes the whole grid at once and returns the total XC energy as a single number, and the
potential comes from automatic differentiation of that number. PyFock therefore evaluates it in three
passes (density over the whole grid → one model call → potential), rather than in the single fused
blocked loop used for LDA/GGA/meta-GGA. The derivatives the model returns are exactly the
vrho/vsigma/vtau intermediates the meta-GGA path already contracts with the AO values, so the
potential assembly itself is unchanged.
Consequences worth knowing:
- Grids must be the native
'treutler'ones (the default). Skala needs both the Becke-partitioned weights and the raw single-atom weights. - Do not density-prune the grid. The model's non-local features are integrals over each atom's full grid.
- The GPU is supported, either way round:
use_gpu=Trueruns the whole SCF on the device, andskala_gpu=Truemoves only the neural functional there and leaves the rest on the CPU. See Skala on the GPU. Forces follow the SCF, souse_gpu=Trueputs both halves of a geometry step on the device. - Closed-shell (restricted) only, like the rest of PyFock's DFT.
- Analytical nuclear gradients are supported, so Skala works for geometry optimization like any other functional — see Analytical Forces & Geometry Optimization. Unlike the semilocal path, the Skala gradient includes the full grid response (Becke weight derivatives and the grid-translation term); its features are integrals over each atomic grid, so a frozen grid would cost ~1e-2 Ha/Bohr rather than the ~1e-4 it costs a meta-GGA.
- Dispersion is off by default — pass
dispersion=Trueto include it, see below. - The model carries about 1e-9 Ha of its own numerical noise. Presenting the network with differently
shaped batches (a different
max_points_per_chunk) shifts the energy at that level. Within one calculation the chunking is fixed, so the shift is systematic rather than random and SCF convergence to1e-8is unaffected — but do not expect two runs with different chunk sizes to agree bit for bit.
Skala 1.1 is parametrised together with a DFT-D3 correction — the checkpoint declares it, and
dftObj.skala.d3_settings() returns 'b3lyp5', meaning D3(BJ) damping with B3LYP5 parameters and no
three-body term. Leaving it out is fine for comparisons against Skala's own reference energies (which
exclude it) but wrong for anything where dispersion matters, such as non-covalent interactions or
conformer ranking.
D3 depends only on the atomic numbers, the coordinates and those damping parameters — not on the density. It never enters the Kohn-Sham matrix and cannot change the SCF, so PyFock evaluates it once after convergence and adds it to the total energy:
pip install dftd3dftObj = DFT(mol, basis, auxbasis, xc='skala-1.1', dispersion=True)
energy, dmat = dftObj.scf() # dispersion-corrected total energy
print(dftObj.Edisp) # the correction on its own, in Hartreedispersion=True uses whatever the functional declares. For any other functional, name the
parametrisation yourself — dispersion='pbe', dispersion='b3lyp' — and tune the damping through
dftObj.dispersion_version (default 'd3bj') and dftObj.dispersion_atm (default False).
pyfock.Dispersion also exposes d3_energy and d3_energy_and_gradient
directly, the latter giving dE_disp/dR for forces. See
examples/ex43_D3_dispersion_correction.py.
The ASE calculator uses the same backend by default:
PyFockCalculator(functional='PBE', dispersion=True, dispersion_kwargs={'xc': 'pbe'})torch-dftd (pip install pyfock[dispersion-gpu]) remains available there for GPU runs, where it
evaluates the correction on the device. Select it with backend='torch-dftd', or implicitly by asking
for a non-CPU device:
dispersion_kwargs={'xc': 'pbe', 'backend': 'torch-dftd', 'device': 'cuda'}Note that Skala's own skala.dispersion module routes D3 through PySCF (pyscf.dispersion.dftd3 or
dftd3.pyscf), which is why PyFock does not use it. PyFock calls dftd3.interface from the same
simple-dftd3 package instead — identical numbers, no PySCF, and Windows wheels are available.
Skala ships the total energies behind its benchmark report as benchmark/reference/measurements.json
(a Git-LFS file, ~7.8 MB): 1221 converged runs over skala-1.1, r2scan, m06-2x and b3lyp5, each at
def2-SVP/TZVP/QZVP, for ~30 molecules from GMTKN55 and a conformer benchmark. The geometries are pulled
from grimme-lab/GMTKN55 at a pinned commit.
One detail is easy to get wrong: the Skala energies there include the D3 correction, because the
runner builds the method through SkalaKS, whose constructor defaults to with_dftd3=True. The plain
PySCF functionals in the same set do not. Comparing a bare PyFock Skala energy against them leaves a
residual exactly equal to the dispersion energy.
benchmarks_tests/validate_skala_reference.py downloads both, runs PyFock under the matching protocol
(spherical orbitals via dftObj.sao = True, density fitting with def2-universal-jkfit, grid level 3,
conv_crit = 5e-6, dispersion=True for Skala only) and tabulates the differences:
python validate_skala_reference.py --molecules H2O --basis def2-tzvpIt runs r2SCAN alongside Skala as a control: PyFock's r2SCAN is independently validated, so whatever it shows against the same reference measures the grid, basis and density-fitting baseline rather than anything to do with Skala. Over H2, H2O, H2O2 and H3N at def2-SVP and def2-TZVP:
| mean abs. difference | max | |
|---|---|---|
| skala-1.1 | 3.2e-08 Ha | 1.1e-07 Ha |
| r2SCAN (control) | 7.0e-08 Ha | 2.2e-07 Ha |
PyFock reproduces the published Skala energies to within the reference data's own run-to-run spread.
tests/test_skala.py keeps one of these values (H2 at def2-SVP) as a permanent regression check.
Skala is several times more expensive per SCF iteration than a semilocal functional. Measured with
benchmarks_tests/benchmark_skala.py (def2-SVP, level-3 grid, 4 CPU cores, density fitting):
| H2O (3 atoms, 25 AOs, 34k grid points) | Caffeine (24 atoms, 260 AOs, 295k grid points) | |
|---|---|---|
| PBE | -76.2740944860 Ha, 7 iter, 0.9 s | -679.1219071931 Ha, 15 iter, 20.1 s |
| r2SCAN | -76.3187302439 Ha, 7 iter, 1.1 s | -679.5341593969 Ha, 12 iter, 29.2 s |
| skala-1.1 | -76.3236300555 Ha, 7 iter, 10.0 s | -679.5460711592 Ha, 14 iter, 194.8 s |
One XC evaluation on an identical density and grid, which isolates the functional from any difference in iteration count:
| H2O | Caffeine | |
|---|---|---|
| PBE | 0.036 s | 1.17 s |
| r2SCAN | 0.109 s | 2.11 s |
| skala-1.1 | 0.791 s | 12.57 s |
| of which the model itself | 0.709 s (90%) | 8.69 s (69%) |
| slowdown vs PBE / r2SCAN | 21.7x / 7.3x | 10.8x / 5.9x |
Two things are worth reading out of this. First, the three-pass restructuring that non-locality forces on PyFock is not where the time goes: the extra density and potential passes cost 0.07 s of H2O's 0.79 s, and the neural network is essentially the whole overhead. Second, the relative cost falls as the system grows (21.7x to 10.8x against PBE), because the model's cost tracks the number of grid points while the semilocal functionals also carry AO work that grows with the basis.
There are two ways to use a GPU, and they compose with the rest of PyFock exactly as they do for any
other functional. use_gpu=True runs the whole SCF on the device — integrals, Coulomb term, AO values,
density, the model and the Vxc assembly:
dftObj = DFT(mol, basis, auxbasis, xc='skala-1.1', dispersion=True, use_gpu=True)skala_gpu=True instead moves only the neural functional and leaves the rest of the SCF on the CPU.
That is useful when the GPU path is unavailable or unwanted, since the model alone is most of Skala's
cost:
dftObj = DFT(mol, basis, auxbasis, xc='skala-1.1', dispersion=True, skala_gpu=True)
atoms.calc = PyFockCalculator(functional='skala-1.1', skala_gpu=True, dispersion=True) # same in ASEBoth need a CUDA build of PyTorch (pip install torch --index-url https://download.pytorch.org/whl/cu128 for current cards); PyFock downloads the CUDA checkpoint, which
is a separate file from the CPU one. use_gpu=True additionally needs CuPy, as it does for every
functional. Skala is a float64 model, so dynamic_precision is switched off for it automatically.
Non-locality is the only thing that changes on the device: Skala cannot be folded into the per-block
loop of eval_xc_3_cupy, where each block calls the functional on its own points, so it gets the same
three-pass driver as on the CPU (Integrals.eval_xc_skala_cupy). Everything inside those passes is the
semilocal GPU code.
benchmarks_tests/benchmark_skala_gpu.py measures the speedup and the CPU/GPU agreement, for
single points and for geometry optimizations. Forces run on the device too -- DFT_Grad follows the
SCF -- so a use_gpu=True geometry optimization never returns to the host for a step; see
Forces on the GPU.
The PyFock + Skala on a Tesla T4 Kaggle notebook shows the GPU setup end to end and benchmarks the PyFock + Skala integration on Kaggle's free T4.
The SCF starts by default from the SANO guess (superposition of atomic natural-orbital
densities): the spherically averaged density of every free atom, written in the ANO-RCC-MB
minimal basis whose contracted functions are the atomic natural orbitals of Roos and
co-workers, projected onto the calculation basis and renormalized shell by shell. It is the
PyFock analogue of PySCF's minao guess, costs a fraction of a second and needs no atomic
SCF. The core-Hamiltonian guess remains available:
dft_obj = DFT(mol, basis, auxbasis, xc='PBE', dmat_guess_method='sano') # default
dft_obj = DFT(mol, basis, auxbasis, xc='PBE', dmat_guess_method='core') # core-Hamiltonian guess
# The guess density itself (CAO representation) and its references
from pyfock import Guess
dmat_guess, info = Guess.sano_dmat(mol, basis)
print(Guess.sano_citation_text(info['nuclear_charges']))The SCF output lists the references to cite for the guess. Compared with the core guess it saves 12-63 % of the SCF iterations, 41 % over a 22-case benchmark (e.g. decane/def2-SVP/PBE 18 -> 8, cholesterol 27 -> 10), and, because PyFock prunes the XC grid with the starting density, it also removes a grid-pruning error of the too-compact core-Hamiltonian density (3e-4 Ha for decane, more for heavy atoms). See docs/sano_guess.md for the method, the benchmark table and the tests.
The exchange-correlation term is integrated on Treutler-Ahlrichs radial grids combined with Lebedev
angular grids (tables from numgrid), pruned region by region and joined by Becke partitioning
with Treutler's atomic-size adjustment. gridsLevel runs from 0 (coarsest) to 9 (finest), default 3.
In our benchmarks the resulting SCF energies agree with PySCF's at the same level to the convergence
threshold, and the grid is built in about the time PySCF needs (decane: 0.11 s vs 0.14 s).
dftObj = DFT(mol, basis, auxbasis, xc='PBE', gridsLevel=3) # default: grids_scheme='treutler'
dftObj.grids_options = {'pruning': None, 'size_adjustment': 'becke'} # variants of the default scheme
dftObj.grids_options = {'points_per_element': {'C': (75, 302)}} # (n_rad, n_ang) overrides per element
dftObj.grids_scheme = 'numgrid'; dftObj.grids_preset = 'compact' # numgrid grids ('dense': the old PyFock grids)
dftObj.use_pyscf_grids = True # or let PySCF build the grid (PySCF must be installed)A GPU calculation (use_gpu=True) also builds the grid on the GPU: the points, their atom indices and
their box order are identical to the CPU build and the weights agree to ~1e-13, at about 6x the speed
from roughly 30 atoms on (decane 0.13 s -> 0.02 s, taxol 3.7 s -> 0.68 s). It falls back to the CPU with
a warning when CuPy or a CUDA device is missing; grids_options={'use_gpu': False} turns it off. The
density pruning that follows the build runs on the GPU too, with the same AO kernel the XC term uses:
it keeps exactly the same points as the CPU pruning at about 4.5x the speed (cholesterol 2.5 s -> 0.55 s).
Standalone grids come from Grids(mol, level=3) (coords, weights and the atom index of every
point, grouped into 1.2 Bohr boxes; use_gpu=True builds them on the GPU). See
docs/xc_grids.md for the construction, its validation and a study of what the
numgrid grids can and cannot do.
The Coulomb term is evaluated with density fitting and Schwarz screening. Three
algorithms are available through DFT.DF_algo:
-
11(default): shell-blocked Rys evaluation with block-sparse storage (CPU/GPU). It honours a memory budget for the stored integrals: -
12: multipole-accelerated density fitting (CPU, pure functionals). The near-field three-center integrals are evaluated and stored as in11; well-separated pairs of a basis-function-product distribution and an auxiliary function are handled through multipole expansions (exact finite moments of the primitive pair products and of the auxiliary functions, box-level expansions truncated atlmax). Energies agree with11to ~1e-8 Hartree. Both the speed-up and the memory saving grow with the extent of the molecule, since the far field is what they come from (def2-SVP, 4 cores, three-center build plus all per-iteration Coulomb work):System far field time vs 11stored integrals + moments Caffeine (14 bohr across) 4.0% 0.9x 0.18 -> 0.19 GB Cholesterol (35 bohr) 32.4% 1.5x 1.61 -> 1.28 GB Icosane C20H42 (48 bohr) 52.6% 1.4x 0.58 -> 0.37 GB Tetracontane C40H82 (95 bohr) 74.7% 2.7x 2.41 -> 0.80 GB The crossover is around 25 to 30 bohr of molecular extent; below it a group of distributions is mostly not expanded at all, because it is only expanded when it has enough far-field auxiliary functions to pay for the expansion.
{'low_memory': True}drops the pre-translated box-centred moments and re-derives them each iteration, which takes icosane from 0.37 to 0.28 GB for about 20% slower Coulomb iterations and identical energies. Parameters are set throughdft_obj.multipole_options, e.g.{'precision': 1e-10, 'lmax': 12, 'box_size': 2.5, 'separation': 4.0}; see docs/df_algo12_multipoles.md. -
10: the previous default, per-function Rys evaluation with sparse triangular storage of the screened three-center integrals. It gives the same energies (identical to ~1e-10 Hartree in the CPU comparisons). It remains selectable for benchmarking.
dft_obj.max_memory_ints3c2e = 2.0 # GB; None (default) = store everything, 0 = recompute every SCF iterationWith a budget smaller than the significant integrals, the most expensive shell-pair
blocks are kept in memory and the cheaper ones are re-evaluated in every SCF cycle.
With use_gpu=True, algorithm 11 evaluates and contracts shell blocks on the GPU
using CuPy and Numba-CUDA. The memory budget limits cached device values. Direct
pairs use an additional temporary buffer (up to 1 GB by default); the plan summary
reports its size. Cached blocks remain on the device even if
keep_ints3c2e_in_gpu=False; use max_memory_ints3c2e to control their storage.
The GPU integral builder supports orbital and auxiliary shells through i (l=6).
See GPU design and validation for validation coverage,
benchmark commands, memory accounting, and limitations of the surrounding driver.
After a converged DFT calculation, analytical nuclear gradients (and forces)
are available directly via DFT_Grad (density fitting; LDA/GGA/meta-GGA and Skala; CPU or GPU):
from pyfock import DFT_Grad
# dftObj must already be converged (dftObj.scf() called)
grad = DFT_Grad(dftObj)
result = grad.calculate()
forces = result["forces"] # (natoms, 3) in Ha/Bohr
gradient = result["gradient"] # = -forcesDFT_Grad follows the SCF: converge with use_gpu=True and the gradient runs on the device too, so
a geometry step never returns to the host. Pass use_gpu explicitly to override that in either
direction:
DFT_Grad(dftObj).calculate() # follows dftObj.use_gpu
DFT_Grad(dftObj, use_gpu=True).calculate() # device gradient after a CPU SCF
DFT_Grad(dftObj, use_gpu=False).calculate() # host gradient after a GPU SCFEvery term has a device implementation except the ECP one, which stays on the CPU. The 3c2e derivative, the fitting coefficients and the nuclear attraction are shell-triplet CUDA kernels; the XC term (semilocal and Skala, with or without the grid response) is CuPy over the grid blocks.
Gradient wall time only, from the same converged SCF (RTX 5070 against 8 CPU cores, PBE,
def2-universal-jfit, level-3 grid):
| molecule | atoms | def2-SVP CPU | def2-SVP GPU | speed-up | def2-TZVP speed-up |
|---|---|---|---|---|---|
| H2O | 3 | 0.08 s | 0.07 s | 1.0x | 1.5x |
| Benzene | 12 | 1.09 s | 0.23 s | 4.7x | 6.7x |
| Caffeine | 24 | 4.57 s | 0.67 s | 6.8x | 9.5x |
| Serotonin | 25 | 3.96 s | 0.64 s | 6.2x | 8.8x |
| Adenine-Thymine | 30 | 7.56 s | 0.94 s | 8.1x | |
| Cholesterol | 74 | 32.0 s | 3.54 s | 9.0x | |
| C60 | 60 | 92.2 s | 10.7 s | 8.6x |
Small molecules do not fill the device -- water breaks even -- and the gain arrives once the shell triplets outnumber the cores by enough, then grows with the basis. The device and host gradients agree to 1e-12 - 1e-11 Ha/Bohr on forces of order 1e-1, which is round-off in the contraction, and against finite differences (grid response on, 1e-3 Bohr step) the GPU gradient is right to 1.0e-6 Ha/Bohr for water and 2.6e-7 for benzene -- the truncation floor of the reference, not of the gradient.
Two functionals sit apart from that picture, for reasons that are properties of the functional rather than of the gradient.
r2SCAN reaches only 2.5 - 2.7x. Its native implementation has no analytic potential: it finite-differences its own energy expression, seven evaluations of ~160 elementwise operations. That is launch-bound on the device -- the same call costs 24 ms on a 20k-point block and 21 ms on a 120k-point one -- so it speeds up ~3.6x while everything around it speeds up ~7x, and Amdahl does the rest. The same finite differencing puts the device/host agreement at ~5e-9 rather than ~1e-12.
Skala reaches ~5x on caffeine, but its per-molecule ratios are worth reading as +-50%: the model
call is a PyTorch forward/backward whose cost varies by an order of magnitude between identical
calls, and with ~7 GB of the card reserved by PyTorch the CuPy pool takes several gradients to reach
a steady state. Serotonin measures 3.0x inside the benchmark suite and 4.6x on its own. Time it with
--repeats, and treat the first two gradients in a process as warm-up.
benchmarks_tests/benchmark_DFT_gradients_gpu.py reproduces all of the above, including the
finite-difference check; tests/test_grad_gpu.py checks each device routine against its CPU
counterpart.
For geometry optimization, use the ASE calculator (requires ase). It uses
the analytical forces by default and falls back to finite differences only for
configurations the analytical gradients do not yet cover (e.g. HF, no DF):
from ase import Atoms
from ase.optimize import BFGS
from pyfock import PyFockCalculator
water = Atoms("OHH", positions=[[0, 0, 0.119], [0, 0.763, -0.477], [0, -0.763, -0.477]])
water.calc = PyFockCalculator(functional="PBE", basis="def2-SVP",
auxbasis="def2-universal-jfit", ncores=4)
BFGS(water).run(fmax=0.02)By default each step runs in a fresh subprocess, which keeps a crashing or non-converging step from
taking the optimizer down and leaves a full PyFock output on disk for every step. The price is that
every step repeats the cold start — the imports, loading the Skala checkpoint and warming up
TorchScript, and a CUDA context when skala_gpu is on — which for small molecules costs more than the
step itself. (PyFock's own Numba kernels are compiled with cache=True, so they reload from disk and
are a small part of it.) run_in_process=True runs the steps here instead and keeps all of that alive
between them:
PyFockCalculator(functional="skala-1.1", run_in_process=True)It is the faster choice for Skala by a wide margin; keep the subprocess for long unattended runs. BLAS
reads its thread count from the environment when numpy is first imported, so with run_in_process=True
set OMP_NUM_THREADS at the top of your script rather than relying on ncores alone. See
examples/ex45_ASE_geometry_optimization_with_Skala.py.
By default the XC gradient treats the quadrature grid as fixed — its dependence on the nuclear
positions is not differentiated. Passing grid_response=True adds the two terms
that removes: the grid points of an atom translating with it, and the Becke weights depending on every
nucleus. The forces then become exactly translationally invariant:
DFT_Grad(dftObj, grid_response=True).calculate()
PyFockCalculator(functional="PBE", basis="def2-SVP", grid_response=True) # same flag in ASE| net force (H2O / def2-SVP) | fixed grid | with grid response |
|---|---|---|
| LDA | 1.3e-05 | 2.9e-14 |
| PBE | 7.8e-06 | 1.5e-14 |
| r2SCAN | 8.9e-05 | 1.9e-14 |
It costs one extra pass over the partitioning and needs the native ('treutler') grids. For semilocal
functionals it is a small correction and off by default, so existing results are unchanged. For Skala it
is mandatory and on by default — its features are integrals over each atomic grid, so a frozen grid
would be wrong by ~1e-2 Ha/Bohr, the size of the forces themselves.
from pyfock import Utils
# Generate cube files for molecular orbitals and density
Utils.write_density_cube(dftObj, filename='benzene_density.cube')PyFock includes a web-based GUI for interactive calculations and visualization:
🌐 Try it online: https://pyfock-gui.bragitoff.com
- Interactive 3D Visualization: View molecules and molecular orbitals using Py3Dmol
- Easy Configuration: Select basis sets, functionals, and calculation parameters
- Automatic Cube File Generation: HOMO, LUMO, and density visualizations
- Input Script Generator: Export Python code for local execution
- PySCF Validation: Built-in comparison with PySCF for accuracy verification
- Molecule Library: Pre-loaded common molecules or custom XYZ input
The GUI source code is available on GitHub and can be run locally:
git clone https://github.com/manassharma07/PyFock-GUI.git
cd PyFock-GUI
pip install -r requirements.txt
streamlit run app.py🚀 Coming Soon: Interactive tutorials on Kaggle and Google Colab
- Kaggle Notebook: Introduction to PyFock
- Kaggle Notebook: Advanced Features and GPU accelerated computations
- Google Colab Notebook: Introduction to PyFock
- Kaggle Notebook: Benchmarking PyFock against PySCF
- Kaggle Notebook: PyFock GPU Dynamic Precision
- Kaggle Notebook: PySCF vs. PyFock GPU Benchmark
- Kaggle Notebook: Skala in PyFock vs. PySCF (CPU): accuracy, speed and geometry optimization
- Kaggle Notebook: PyFock + Skala on the GPU (Tesla T4): setup and benchmark
📚 Full Documentation: https://pyfock-docs.bragitoff.com
- Density Fitting with Cauchy-Schwarz screening
- GPU acceleration for integrals and XC evaluation
- DIIS convergence acceleration
- Web-based GUI
- Rys quadrature (roots 1–10)
- Analytical nuclear gradients & forces (density fitting; LDA/GGA/meta-GGA; CPU)
- ASE calculator & geometry optimization
- Analytical gradients on GPU and for non-DF / ECP calculations
- Electron dynamics & Excited state calculations (RT-TDDFT)
- Periodic boundary conditions
- Hybrid functionals with exact exchange (native B3LYP/PBE0 and LibXC hybrids, RI-K via DF_algo=11; CPU)
- Skala neural exchange-correlation functional, on CPU and GPU, with analytical gradients (CPU)
- Grid-response XC gradients (Becke weight derivatives,
Grids.becke_weight_gradient) - Multi-GPU parallelization
- Basis set optimization tools
See the open issues for a full list of proposed features and known issues.
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make PyFock better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Don't forget to give the project a star! ⭐ Thanks!
Distributed under the MIT License. See LICENSE file for more information.
If you use PyFock in your research, please cite:
@article{sharma2026pyfock,
title = {PyFock: A Just-In-Time Compiled Gaussian Basis DFT Python Code for CPU and GPU Architectures},
author = {Sharma, Manas and Sierka, Marek},
journal = {The Journal of Physical Chemistry A},
year = {2026},
month = {08},
issn = {1089-5639},
doi = {10.1021/acs.jpca.6c03727},
url = {https://doi.org/10.1021/acs.jpca.6c03727},
eprint = {https://pubs.acs.org/jpcafh/article-pdf/doi/10.1021/acs.jpca.6c03727/67108190/acs.jpca.6c03727.pdf},
}PyPI Package: https://pypi.org/project/pyfock/
Manas Sharma
- Email: manas.sharma@uni-jena.de
- Website: manas.bragitoff.com
- LinkedIn: linkedin.com/in/manassharma07
- Project Homepage: https://pyfock.bragitoff.com
- Project Link: https://github.com/manassharma07/pyfock
Built With
