Skip to main content

Crate cuest_cudarc

Crate cuest_cudarc 

Source
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 CuestContext wraps a user-supplied Arc<CudaStream>. The stream is bound once at cuestCreate (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 as CuestError::Rust otherwise.
  • Objects keep their dependencies alive (an Arc-based keep-alive, not lifetime parameters), so cuEST’s destruction order is enforced by normal Drop order.
  • Device buffers for inputs are taken as &impl DevicePtr<f64> (any CudaSlice, CudaView or CudaViewMut); outputs are either auto-allocated or written into an &mut impl DevicePtrMut<f64> (*_into methods). 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 unsafe blocks 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_loading feature): the cuEST shared library is loaded at runtime; set CUEST_DYLOAD or see cuest_ffi::ffi_dynamic for the search order.
  • Static linking (--no-default-features): links libcuest at build time (CUEST_LIB_DIR overrides 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.

ResourcesBadges
CrateCrate
API DocumentAPI Documentation
RepositoryRepository
Bound librarycuEST v0.2.2

§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);
  • CuestOeIntPlan with 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_loading feature): the cuEST shared library is loaded at runtime; set CUEST_DYLOAD to the library path, or let cuest-ffi search standard locations.
  • Static linking (--no-default-features): links libcuest at build time (CUEST_LIB_DIR overrides 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 -- --ignored

They 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.