Expand description
Safe Rust wrapper for cuEST (CUDA Electronic Structure Toolkit) built on cudarc.
This crate wraps the raw cuest_ffi bindings into a safe API: cuEST
handles become owned Rust objects, user-allocated workspaces are managed
internally, every return code is checked, and device buffers are tied to
the cudarc stream the cuEST handle was created on.
This crate is not an official NVIDIA project. cuEST itself is proprietary NVIDIA software and is not distributed with this crate.
§Design notes
- A
CuestContextwraps a user-suppliedArc<CudaStream>. The stream is bound once atcuestCreate(cuEST has no per-call streams), so every device buffer passed to a compute call must live on that stream; this is checked at runtime and reported asCuestError::Rustotherwise. - Objects keep their dependencies alive (an
Arc-based keep-alive, not lifetime parameters), so cuEST’s destruction order is enforced by normalDroporder. - Device buffers for inputs are taken as
&impl DevicePtr<f64>(anyCudaSlice,CudaVieworCudaViewMut); outputs are either auto-allocated or written into an&mut impl DevicePtrMut<f64>(*_intomethods). No synchronization is performed inside compute methods — the work is stream-ordered, as with cudarc itself. - All calls are error-checked; a failing cuEST status becomes
CuestError::Status. Buffer sizes the cuEST C API cannot validate (the caller contract) are validated here instead. - The
unsafeblocks are cuEST FFI calls (justified by owned handles, the cuEST call contracts, and the validations above) and one uninitialized scratch allocation (cuEST workspace buffers, matching the C samples’cudaMalloc).
§Example
use cuest_cudarc::prelude::*;
use cuest_cudarc::cudarc::driver::CudaContext;
let stream = CudaContext::new(0)?.default_stream();
let context = CuestContext::new(&stream)?;
// H2 at 1.4 Bohr (0.7408 Angstrom), STO-3G, on one atom each.
let xyz = Xyz::parse(
"2
H2
H 0.0 0.0 0.0
H 0.0 0.0 0.7408
",
)?;
let spec = CuestShellSpec::new(0, true, vec![3.42525091, 0.62391373, 0.16885540], vec![
0.15432897, 0.53532814, 0.44463454,
])?
.normalized(1.0)?;
let shells: Vec<CuestAOShell> =
(0..xyz.len()).map(|_| CuestAOShell::new(&context, &spec)).collect::<Result<_, _>>()?;
let basis = CuestAOBasis::new(&context, &[&shells[0..1], &shells[1..2]])?;
let pair_list = CuestAOPairList::from_xyz(&basis, &xyz, 1.0e-14)?;
let plan = CuestOeIntPlan::new(&basis, &pair_list)?;
let s = plan.overlap()?; // CudaSlice<f64>, nao x nao, on `stream`
let t = plan.kinetic()?;§Loading modes
The loading mode is selected through this crate’s features (matching
cuest-ffi):
- Dynamic loading (default,
dynamic_loadingfeature): the cuEST shared library is loaded at runtime; setCUEST_DYLOADor seecuest_ffi::ffi_dynamicfor the search order. - Static linking (
--no-default-features): linkslibcuestat build time (CUEST_LIB_DIRoverrides the search path).
§Thread safety
cuEST documents no thread-safety guarantees beyond the usage model of one
handle per stream. The types in this crate are therefore Send (they can
be moved between threads) but not Sync: a given handle (and any
object derived from it) must be used from one thread at a time. The
underlying CUDA stream is the real serialization point.
§Escape hatch
The raw bindings are re-exported as cuest_ffi for API surface this
crate does not wrap yet; every object also exposes its raw handle
(raw_handle()).
§cuest-cudarc
Safe Rust wrapper for cuEST (CUDA Electronic Structure Toolkit) on top of cudarc, the safe CUDA driver API crate.
This crate is not an official NVIDIA project. cuEST itself, including its
headers and shared library, is proprietary NVIDIA software and is not
distributed with this crate; obtain it from NVIDIA. The raw bindings live in
the cuest-ffi crate of this workspace.
§Usage
use cuest_cudarc::prelude::*;
use cuest_cudarc::cudarc::driver::CudaContext;
let stream = CudaContext::new(0).unwrap().default_stream();
let context = CuestContext::new(&stream).unwrap();
// H2, STO-3G: one s shell per atom.
let xyz = Xyz::parse("2\nH2\nH 0.0 0.0 0.0\nH 0.0 0.0 0.7408\n").unwrap();
let spec = CuestShellSpec::new(
0,
true,
vec![3.42525091, 0.62391373, 0.16885540],
vec![0.15432897, 0.53532814, 0.44463454],
)
.unwrap()
.normalized(1.0)
.unwrap();
let shells: Vec<CuestAOShell> =
(0..xyz.len()).map(|_| CuestAOShell::new(&context, &spec).unwrap()).collect();
let basis = CuestAOBasis::new(&context, &[&shells[0..1], &shells[1..2]]).unwrap();
let pair_list = CuestAOPairList::from_xyz(&basis, &xyz, 1.0e-14).unwrap();
let plan = CuestOeIntPlan::new(&basis, &pair_list).unwrap();
let s = plan.overlap().unwrap(); // CudaSlice<f64>, nao x nao, on `stream`
let t = plan.kinetic().unwrap();The wrapper owns all cuEST handles (with correct destruction order enforced by
Arc-based keep-alives), allocates and frees the host/device workspaces
required by every create/compute call, validates the buffer sizes the cuEST C
API cannot check, and verifies that every device buffer lives on the CUDA
stream the context is bound to. Compute methods are stream-ordered and do not
synchronize; use cudarc to read results back.
§Scope
Wrapped by this crate today (the v1 slice):
CuestContext(handle creation, math mode, compute-capability target, version queries);CuestAOShell/CuestAOBasis/CuestAOPairList(basis construction with the cuEST coefficient normalization);CuestOeIntPlanwith overlap (S), kinetic (T) and potential (V) matrix computation;- an XYZ parser for geometries (Angstrom input, Bohr conversion).
Not wrapped yet (use the raw cuest_ffi bindings
through the escape hatches — every object exposes raw_handle()): density
fitting (J/K), exchange-correlation, ECP, grids, PCM, multipole/nabla/angular
momentum integrals and all derivative computations.
§Contraction coefficients
cuEST uses primitive exponents and coefficients verbatim: the coefficients
must include the primitive normalization (relative to raw, unnormalized
primitives), which is not the convention of standard basis files. Use
CuestShellSpec::normalized(normalization) (resp.
cuest_cudarc::shell::normalize_coefficients) to convert coefficients taken
from a basis file (.gbs and friends); do not normalize twice.
§Loading modes
- Dynamic loading (default,
dynamic_loadingfeature): the cuEST shared library is loaded at runtime; setCUEST_DYLOADto the library path, or letcuest-ffisearch standard locations. - Static linking (
--no-default-features): linkslibcuestat build time (CUEST_LIB_DIRoverrides the search path).
§Thread safety
Types are Send but not Sync: handles may be moved between threads but must
be used from one thread at a time (the CUDA stream is the serialization
point). cuEST documents no further thread-safety guarantees.
§Testing
Tests are split by name. The pure-Rust unit tests are cpu_* — no GPU and no
cuEST library needed — and are what CI runs (cargo test --workspace cpu). The
end-to-end tests are gpu_* and #[ignore]d; they need a GPU plus a local
cuEST installation:
CUEST_DYLOAD=/path/to/libcuest.so LD_LIBRARY_PATH=/usr/local/cuda/lib64 \
cargo test -p cuest-cudarc gpu -- --ignoredThey compare S/T/V matrices for H2/STO-3G and H2O/def2-SVP against
pyscf reference values in tests/regression/reference.toml (regenerate with
tests/regression/gen_reference.py). The tests set the JIT kernel cache to
target/cuest-jit-cache (cuEST otherwise requires HOME); in restricted
environments pass CuestHandleOptions::jit_cache_dir explicitly.
§AI assistance
This project is developed with the help of AI assistance (mainly using Claude Code with customized LLM models). See CLAUDE.md and git history for details.
§License
Apache-2.0. This project is not affiliated with NVIDIA Corporation; the cuEST library and its headers are licensed separately by NVIDIA under the NVIDIA cuEST license agreement.
Re-exports§
pub use crate::basis::CuestAOBasis;pub use crate::context::CuestContext;pub use crate::context::CuestHandleOptions;pub use crate::context::CuestMathMode;pub use crate::error::CuestError;pub use crate::oeint::CuestFfloatUsageMode;pub use crate::oeint::CuestJitUsageMode;pub use crate::oeint::CuestOeIntPlan;pub use crate::oeint::CuestPotentialComputeOptions;pub use crate::pair_list::CuestAOPairList;pub use crate::shell::CuestAOShell;pub use crate::shell::CuestShellSpec;pub use crate::xyz::Xyz;pub use cudarc;pub use cuest_ffi;
Modules§
- basis
- Atomic orbital (AO) basis sets.
- context
- The cuEST context: the top-level handle every other object is derived from.
- error
- Error types for cuEST operations.
- oeint
- One-electron integral plans and computation (overlap, kinetic, potential).
- pair_
list - AO pair lists: sparsity discovery for a basis at a fixed geometry.
- prelude
- Common imports.
- query
- Typed queries of cuEST object attributes (
cuestQuery). - shell
- Atomic orbital (AO) Gaussian shells.
- xyz
- Minimal XYZ geometry parsing and unit helpers.