Imported from jeanwsr/pyscf-skills (
pyscf-general/SKILL.md). Install upstream withnpx skills add jeanwsr/pyscf-skills --skill pyscf-general. Copyright stays with the author.
PySCF Development
Project Overview
PySCF is a Python-based quantum chemistry framework. The package lives under pyscf/ with C/C++ extensions compiled via CMake. The top-level pyscf/__init__.py exports version and plugin discovery; computational modules are organized by method family.
Module Hierarchy
| Submodule | Purpose |
|---|---|
pyscf/lib/ |
Foundation: StreamObject base class, C-extension loader, numpy/linalg wrappers, logging, DIIS, chkfile I/O, parameters |
pyscf/gto/ |
Molecular structure, basis set parsing, libcint integral interface |
pyscf/scf/ |
Hartree-Fock (RHF, UHF, ROHF, GHF, DHF) |
pyscf/dft/ |
Kohn-Sham DFT (inherits from SCF) |
pyscf/cc/ |
Coupled cluster (CCSD, CCSDT, CCSDTQ, EOM) |
pyscf/ci/ |
Configuration interaction (CISD) |
pyscf/mcscf/ |
CASCI, CASSCF (1-step and 2-step) |
pyscf/mcpdft/ |
MC-PDFT (on-top pair-density functionals) |
pyscf/df/ |
Density fitting / RI |
pyscf/ao2mo/ |
AO-to-MO integral transformation |
pyscf/pbc/ |
Periodic boundary conditions (mirrors molecular module hierarchy) |
pyscf/tools/ |
FCIDUMP, molden, cubegen exporters |
pyscf/solvent/ |
Continuum solvation |
pyscf/fci/ |
Full CI solvers |
pyscf/mp/ |
MP2, MP3, MP4 |
pyscf/tdscf/ |
Time-dependent HF/DFT |
pyscf/grad/ |
Nuclear gradients |
pyscf/hessian/ |
Hessians |
pyscf/geomopt/ |
Geometry optimization |
pyscf/x2c/ |
Exact two-component relativistic methods |
pyscf/symm/ |
Point-group symmetry |
pyscf/agf2/ |
Algebraic Green's function GW |
pyscf/adc/ |
Algebraic diagrammatic construction |
pyscf/mrpt/ |
Multi-reference perturbation theory |
pyscf/eph/ |
Electron-phonon coupling |
pyscf/data/ |
Physical constants, element data |
Key Abstractions
StreamObject (pyscf/lib/misc.py)
The base class for nearly all computational objects. Provides a fluent "pipe" API:
.set(**kwargs)— updates attributes viasetattr, returnsself.__call__is aliased to.set..run(*args, **kwargs)— calls.set(**kwargs)then.kernel(*args), returnsself..kernel(*args, **kwargs)— the main computational driver; each subclass overrides this..apply(fn, *args, **kwargs)— appliesfntoself:return fn(self, *args, **kwargs)..check_sanity()— validates attributes against_keysset, warns on misspelled/overwritten attributes..copy()— shallow copy viaview(self.__class__)._keys— class attribute listing valid attribute names.
Example: mol.apply(scf.RHF).run(conv_tol=1e-5).apply(mcscf.CASSCF, 6, 4)
Mole and __getattr__ dispatch (pyscf/gto/mole.py)
mol.RHF(), mol.CCSD(), etc. work through Mole.__getattr__ which:
- Imports
pyscf.__all__(triggers lazy loading of all modules) - Looks up the key in
dft, thenscfmodules - Returns a
_MoleLazyCallAdapterwrapping the factory function
So mol.RHF() → scf.RHF(mol), mol.CCSD() → scf.HF(mol).run().CCSD().
Chkfile auto-creation (pyscf/scf/hf.py:1772-1775)
In SCF.__init__:
if MUTE_CHKFILE:
self.chkfile = None
else:
self._chkfile = lib.NamedTemporaryFile(dir=lib.param.TMPDIR)
self.chkfile = self._chkfile.name
When MUTE_CHKFILE is False (default), every SCF object auto-creates a temp chkfile. The _chkfile handle keeps the file alive. Downstream classes (CASSCF, CCSD, TDSCF, AGF2, MC-PDFT) inherit chkfile via self.chkfile = self._scf.chkfile or self.chkfile = mf.chkfile.
CI sets scf_hf_SCF_mute_chkfile = True in .pyscf_conf.py, making all auto-chkfiles None. Tests that read .chkfile MUST explicitly assign one.
Configuration (pyscf/__config__.py)
Loaded from first found of: PYSCF_CONFIG_FILE env var, ./.pyscf_conf.py, ~/.pyscf_conf.py. Key variables defined in pyscf/lib/parameters.py:
TMPDIR— scratch file locationMAX_MEMORY— memory limit in MBLIGHT_SPEED,BOHR— physical constants
Modules read config via getattr(__config__, 'key', default).
libxc Custom Functionals (pyscf/dft/libxc.py)
register_custom_functional_ API
register_custom_functional_(new_name, based_on_xc_code,
ext_params={libxc_id: array_or_dict},
omega=[omega_value],
hyb=(sr_hyb, sr_hf, omega))
Registers a custom functional under new_name by cloning based_on_xc_code then applying customizations. Stored in module-level _CUSTOM_FUNC_R / _CUSTOM_FUNC_U dicts and accessed via _get_xc().
Named-param ext_params
XCFunctionalCache.customize_ accepts dict-format ext_params alongside traditional positional arrays:
# Positional array (traditional):
ext_params = {202: numpy.array([0.15, 0.88491, ...])}
# Named-param dict:
ext_params = {202: {'_b': 0.15, '_c': 0.88491, ...}}
Dict-format uses xc_func_set_ext_params_name to set each parameter individually by name, avoiding libxc's positional-order dependency. Validation via LIBXC_xc_func_find_ext_params_name — unknown names raise ValueError.
C wrapper (pyscf/lib/dft/libxc_itrf.c)
LIBXC_xc_func_find_ext_params_name wraps libxc's static xc_func_find_ext_params_name. Returns index ≥ 0 if the param name is valid for the functional, -1 otherwise.
ctypes bindings
_itrf.xc_func_set_ext_params_name.argtypes = (ctypes.c_void_p, ctypes.c_char_p, ctypes.c_double)
_itrf.xc_func_set_ext_params_name.restype = None
_itrf.xc_func_get_ext_params_name.argtypes = (ctypes.c_void_p, ctypes.c_char_p)
_itrf.xc_func_get_ext_params_name.restype = ctypes.c_double
_itrf.LIBXC_xc_func_find_ext_params_name.argtypes = (ctypes.c_void_p, ctypes.c_char_p)
_itrf.LIBXC_xc_func_find_ext_params_name.restype = ctypes.c_int
Bindings go at module level in libxc.py, alongside existing _itrf.* bindings.
Validating param names
Use LIBXC_xc_func_find_ext_params_name before xc_func_set_ext_params_name:
if _itrf.LIBXC_xc_func_find_ext_params_name(func, name.encode()) < 0:
raise ValueError(f"Unknown ext_params name '{name}'")
Note: xc_func_get_ext_params_name returns 0.0 for nonexistent names (not NaN), so it cannot be used for validation.
Registered functional lifetime
Custom functionals persist in _CUSTOM_FUNC_R / _CUSTOM_FUNC_U until explicitly removed via unregister_custom_functional_(). The XCFunctionalCache objects hold references to C-level functional pointers managed by libxc. Registration and unregistration perform C-level init/end respectively.
Test Conventions
File structure
- Tests in
test/subdirectory of each module:pyscf/scf/test/test_rhf.py - Single
KnownValues(unittest.TestCase)class per test file - Module-level
setUpModule()/tearDownModule()for shared expensive objects
Patterns
def setUpModule():
global mol, mf
mol = gto.M(verbose=7, output='/dev/null', atom='...', basis='cc-pvdz')
mf = scf.RHF(mol)
mf.conv_tol = 1e-10
mf.chkfile = lib.NamedTemporaryFile().name
mf.kernel()
def tearDownModule():
global mol, mf
mol.stdout.close()
del mol, mf
Assertions
self.assertAlmostEqual(value, expected, ndigits)— primary assertionlib.fp(array)— deterministic array fingerprint:dot(cos(arange(n)), a.ravel())_high_costand_skipsuffixes exclude tests from default runs (configured inpytest.ini)@unittest.skipIf(condition, reason)for optional dependencies
Chkfile in tests
- CI sets
MUTE_CHKFILE = True→ auto-chkfiles areNone. Tests MUST explicitly setmf.chkfile = lib.NamedTemporaryFile().nameif.chkfileis read later. - Tests that don't read
.chkfiledon't need explicit assignment. test_ghf.pyhasscf.hf.MUTE_CHKFILE = Trueat module top — the only test file that globally disables auto-chkfile.- Use
os.path.jointo construct expected paths in assertions, never hardcode/or\.
Running tests
pytest pyscf/scf/test/ # default (skips _high_cost, _skip)
pytest pyscf/scf/test/ -k "_high_cost" # include expensive
pytest pyscf/scf/test/test_rhf.py::KnownValues::test_init_guess_chk
@test subagent
Use the @test subagent to run tests with automatic ruff linting:
@test auto— auto-discovers which tests to run based ongit diff@test pyscf/scf/test/test_rhf.py -v— specific test file@test pyscf/scf/test/ -v— whole test directory
Linting
ruff check --config .ruff.toml pyscf
flake8 pyscf # test directories excluded
Import Conventions
from pyscf import lib(notimport pyscf.lib)from pyscf import gto, scf, dft, cc, mcscf- Submodules:
from pyscf.scf import hffor direct class access pyscf/__all__.pyprovides lazy imports for post-HF modules — not loaded atimport pyscftime
Build System
C extensions compiled via CMake (pyscf/lib/CMakeLists.txt), invoked by custom CMakeBuildPy in setup.py. Key extensions:
libnp_helper— OMP threads, BLAS-wrapped array opslibcvhf— J/K matrix constructionlibcgto/libcint— Gaussian integrals (wraps qcint/libcint v6.1.3)libdft— XC functional gridslibao2mo— integral transformationlibpbc— periodic integralslibxc_itrf— libxc C interface wrapper (functional init/end, eval_xc, param setters/getters)
Build deps: CMake 3.22+, BLAS, numpy, scipy, h5py.
Rebuilding after C changes
cd /path/to/pyscf/pyscf/lib/build/dft && cmake --build .
CI environment
OMP_NUM_THREADS=4.pyscf_conf.pysetsscf_hf_SCF_mute_chkfile = True,TMPDIR = "./pyscftmpdir"- Tests fail if temp files remain in
pyscftmpdirafter run
GitHub CLI (gh)
The GitHub CLI is available for PR review, branch management, and CI inspection. The repo is pyscf/pyscf.
Pull request review
# View PR summary and metadata
gh pr view 3255 --repo pyscf/pyscf
# View PR as JSON (title, author, files, state, etc.)
gh pr view 3255 --repo pyscf/pyscf --json title,body,author,files,additions,deletions,state,headRefName
# Full unified diff
gh pr diff 3255 --repo pyscf/pyscf
# Check out a PR branch locally
gh pr checkout 3255 --repo pyscf/pyscf
# List review comments on a PR
gh api repos/pyscf/pyscf/pulls/3255/comments
Viewing CI checks
# List CI runs for a branch
gh run list --repo pyscf/pyscf --branch high_order_cc_improvements --limit 5
# Watch a specific CI run as it progresses
gh run watch <run-id> --repo pyscf/pyscf
Issue workflows
# List / filter open issues
gh issue list --repo pyscf/pyscf --limit 20
gh issue list --repo pyscf/pyscf --label bug
gh issue list --repo pyscf/pyscf --search "libxc"
# View an issue (with discussion thread)
gh issue view 2864 --repo pyscf/pyscf --comments
# JSON output for scripting
gh issue view 2864 --repo pyscf/pyscf --json title,body,labels,state,comments
# Cross-referenced PRs/commits for an issue
gh api repos/pyscf/pyscf/issues/2864/timeline
Common patterns
- When reviewing a PR, start with
gh pr viewfor metadata, thengh pr difffor the code changes. - After reviewing the diff,
gh pr checkoutto test locally. gh run list+gh run watchto track CI status for a PR branch.- When investigating a bug report:
gh issue view --commentsfirst, then search for duplicates/related issues, then check the timeline for linked PRs.
Common Pitfalls
-
MUTE_CHKFILE: CI disables auto-chkfiles. Tests reading
.chkfilemust assign explicitly:mf.chkfile = lib.NamedTemporaryFile().name. -
Path separators: use
os.path.joinin both production and test code. Never hardcode/or\. -
__setstate__sets chkfile to None:generate_pickle_methods(excludes=('chkfile', ...))in SCF and derived classes setschkfile = Noneon unpickle. Deserialized objects need explicit chkfile reassignment. -
NamedTemporaryFile().namewithout storing handle: the file handle must be kept alive until all I/O is complete. Inline.nameis fine for read-once patterns but the file object must persist through writes. -
mol.HF()always callsSCF.__init__: via__getattr__dispatch. Auto-chkfile is created unlessMUTE_CHKFILEis set. -
H5TmpFilewith explicit filename: whenfilename=None(default),delete_on_close=Trueand the file is auto-cleaned. When an explicit filename is passed,delete_on_close=False. -
Basis parser paths: basis set paths are resource paths within the package, constructed with
os.path.join. Tests must useos.path.joinfor expected paths, not hardcoded separators. -
xc_func_set_ext_params_nameexpects lowercase: libxc parameter names are lowercase by convention (_alpha,_beta). Passing uppercase names silently fails — the C function is case-sensitive. -
xc_func_get_ext_params_namereturns 0.0 for unknowns: invalid param names return 0.0 (not NaN). UseLIBXC_xc_func_find_ext_params_name(returns -1) to validate before setting.