A pure-Rust library of exchange–correlation (XC) functionals for
density-functional theory (DFT), built around automatic differentiation:
each functional is written once as a single scalar energy expression, and all
derivatives — vxc and second derivatives fxc, spin-polarized and
unpolarized — are generated by forward-mode AD
(num-dual), so they are correct by
construction. No C dependency, no build-time toolchain requirements, trivial
cross-compilation.
xcx began as a Rust port of libxc and has grown into its own project. It keeps libxc's functional IDs and numerical conventions for drop-in interoperability — and is golden-verified against libxc to ≤ 1e-10 where the two overlap — but it is not a mirror: xcx ships functionals libxc does not (notably a double-hybrid family with full PT2 metadata), exposes richer machine-readable metadata, and adds new functionals clean-room from the literature rather than by porting.
- AD-first architecture. One scalar energy expression per functional; the
AD harness derives every
vxc/fxccomponent. No hand-written or code-generated derivative code to maintain, and second derivatives come for free for every functional. - Double hybrids as first-class citizens. B2PLYP, revDSD-PBEP86-D4,
PWPB95, and ωB97M(2) — none of which exist in libxc — with EXX fractions,
same/opposite-spin PT2 coefficients, and CAM parameters exposed via a
structured
double_hybrid()metadata API (xcx-private id namespace ≥ 100000, documented indocs/api-convention.md). - Metadata, not magic. Hybrid EXX fractions, range-separation (CAM ω/α/β), VV10 (b/C), and PT2 coefficients are all structured metadata, so a host code can assemble any modern functional from xcx's semilocal part plus its own integrals.
- A frozen, semver-stable data contract.
docs/api-convention.mdpins the API, array packing, and metadata semantics. - Pure Rust, dependency-light. The published crate depends only on
num-dual,nalgebra, andlibm. It compiles anywhere Rust does — WASM, embedded targets, cross-compiled HPC nodes — with no C toolchain.
Given a density (and, depending on the functional, its gradient and/or kinetic
energy density), xcx returns the XC energy per particle, its first and
second derivatives (vxc and fxc), plus rich metadata (family, input
requirements, exact-exchange fraction, range-separation, VV10, and PT2
parameters).
xcx maps `(rho, sigma, tau[, lapl]) → energy density + derivatives + metadata
- linear mixing
and nothing else. It deliberately contains **no** integration grids, atomic-orbital evaluation, SCF driver, or dispersion correction. For hybrids, VV10, and double hybrids it **exposes the parameters** (EXX fraction, CAM ω/α/β, VV10 b/C, PT2 coefficients) so the host electronic-structure code can build those terms;xcx` never computes the exact-exchange, nonlocal- correlation, or perturbative integrals.
See docs/api-convention.md for the full,
semver-stable data/ABI contract.
[dependencies]
xcx = "0.3"MSRV: Rust 1.87.
use xcx::{Functional, FunctionalId, Spin, XcInput};
fn main() -> Result<(), xcx::XcError> {
// Spin-unpolarized LDA exchange over three grid points.
let f = Functional::new(FunctionalId::LdaX, Spin::Unpolarized)?;
let rho = [0.1_f64, 0.2, 0.3];
let out = f.eval(rho.len(), &XcInput::lda(&rho))?;
// out.exc[i] = XC energy per particle ε_xc at point i
// out.vrho[i] = ∂(n·ε_xc)/∂n at point i
println!("{:?}", out.exc);
Ok(())
}All with energy, all first derivatives, and second derivatives (fxc), in
both spin-polarized and unpolarized modes. The id column is the
libxc-compatible numeric id (xcx-private ids ≥ 100000 are marked *).
| Family | Functional | id | Notes |
|---|---|---|---|
| LDA | lda_x |
1 | Slater exchange |
| LDA | lda_c_pw |
12 | PW92 correlation |
| LDA | lda_c_vwn |
7 | VWN5 |
| LDA | lda_c_vwn_3 |
30 | VWN3 |
| LDA | lda_c_vwn_rpa |
8 | VWN5 (RPA) |
| GGA | gga_x_pbe |
101 | PBE exchange |
| GGA | gga_x_pbe_r |
102 | revPBE exchange |
| GGA | gga_x_pbe_sol |
116 | PBEsol exchange |
| GGA | gga_x_rpbe |
117 | RPBE exchange (exponential enhancement) |
| GGA | gga_c_pbe |
130 | PBE correlation |
| GGA | gga_c_pbe_sol |
133 | PBEsol correlation |
| GGA | gga_x_b88 |
106 | Becke 88 exchange |
| GGA | gga_c_lyp |
131 | Lee–Yang–Parr correlation |
| GGA | gga_c_p86 |
132 | Perdew 86 correlation (PZ81 + gradient term) |
| GGA | gga_xc_b97_3c |
327 | B97-3c xc part (Becke-97 series refit; host adds D3/SRB) |
| meta-GGA | mgga_x_tpss |
202 | TPSS exchange |
| meta-GGA | mgga_c_tpss |
231 | TPSS correlation |
| meta-GGA | mgga_x_r2scan |
497 | r²SCAN exchange |
| meta-GGA | mgga_c_r2scan |
498 | r²SCAN correlation |
| meta-GGA | mgga_x_m06_l |
203 | M06-L exchange |
| meta-GGA | mgga_c_m06_l |
233 | M06-L correlation |
| meta-GGA | mgga_c_m06_2x |
236 | M06-2X correlation |
| meta-GGA | mgga_xc_b97m_v |
254 | B97M-V (semilocal part; VV10 params via metadata) |
| Hybrid | hyb_gga_xc_b3lyp |
402 | B3LYP — uses VWN_RPA (matching libxc 402) |
| Hybrid | hyb_gga_xc_b3lyp5 |
475 | B3LYP/VWN5 |
| Hybrid | hyb_gga_xc_pbeh |
406 | PBE0 |
| Hybrid | hyb_mgga_x_m06_2x |
450 | M06-2X exchange (54% EXX) |
| Hybrid | hyb_mgga_xc_pw6b95 |
451 | PW6B95 (28% EXX) |
| Hybrid | hyb_gga_xc_pbeh_3c |
100005* | PBEh-3c xc part (modified-PBE hybrid, 42% EXX; host adds gCP/D3) |
| RS hybrid | hyb_gga_xc_wb97x_v |
466 | ωB97X-V (SR semilocal part; CAM + VV10 via metadata) |
| RS hybrid | hyb_mgga_xc_wb97m_v |
531 | ωB97M-V (SR semilocal part; CAM + VV10 via metadata) |
| Double hybrid | hyb_gga_xc_b2plyp |
100001* | B2PLYP (EXX 0.53; PT2 0.27/0.27 via metadata) |
| Double hybrid | hyb_gga_xc_revdsd_pbep86_d4 |
100002* | revDSD-PBEP86-D4 core (EXX 0.69; PT2 0.5922/0.0636) |
| Double hybrid | hyb_mgga_xc_pwpb95 |
100003* | PWPB95 (EXX 0.50; SOS-PT2 0.269) |
| Double hybrid | hyb_mgga_xc_wb97m_2 |
100004* | ωB97M(2) (CAM + PT2 via metadata; clean-room) |
* Functionals with no libxc counterpart (the double hybrids and PBEh-3c)
use the xcx-private id namespace (≥ 100000) documented in
docs/api-convention.md. xcx emits the scaled semilocal mix; the host adds
EXX/CAM and (for double hybrids) the PT2 term from double_hybrid() metadata.
Beyond the named functionals, three parameterized public constructors
expose the underlying families as data: Functional::pbe_x(κ, μ, spin),
Functional::pbe_c(β, spin) (recover the named PBE-family members at their
published parameters), and Functional::b97_xc(c_x, c_ss, c_os, spin)
(the Becke-1997 power series, generic over the coefficient count).
The meta-GGA Laplacian (lapl) path is planned via the same AD framework.
Correctness is checked two ways:
- Public, dependency-free tests (run in CI, no libxc needed): finite-difference self-consistency of derivatives, polarized/unpolarized consistency, analytic spot-checks, and a fuzz gate asserting finite energy and every derivative component (no NaN/Inf/panic) across the physical input range for all functionals, both spins.
- Golden cross-check vs. libxc (
crates/xcx-validation, never published): where a functional overlaps libxc, values are compared against snapshots generated from a pinned conda-forge libxc (6.1.0) to ≤ 1e-10 relative, plus an end-to-end SCF cross-check on real integration grids. Functionals with no libxc counterpart (the double hybrids) are verified by parameterized re-evaluation through libxc'sxc_func_set_ext_paramswhere possible, and by literature reproduction otherwise. The snapshots are committed, so CI runs deterministically without libxc present.
This is a Cargo workspace with two crates:
crates/xcx— the published library. Its only dependencies arenum-dual,nalgebra, andlibm; it carries no C/FFI surface of any kind.crates/xcx-validation— an unpublished (publish = false) crate that cross-checksxcxagainst the reference C libxc. The libxc FFI (libloading, behind thelibxc-ffifeature), the committed reference snapshots, and the snapshot-regeneration tools all live here on purpose, so the publishedxcxcrate stays dependency-light and its packaged artifact can never include test data. Seecrates/xcx-validation/README.md.
The everyday xcx tests (unit, finite-difference, and the fuzz gate) live in
crates/xcx itself and run in CI without libxc.
Where xcx and libxc implement the same functional, xcx reproduces pinned libxc
to ≤ 1e-10 — even where libxc is the less accurate of the two —
so results are interchangeable and ids/names can be used drop-in. The handful
of intentional, documented numerical divergences (all outside the
physically-relevant domain or below the golden tolerance) are described in
docs/api-convention.md.
Beyond that overlap, xcx is an independent project with its own roadmap,
functionals, and metadata model.
xcx is licensed per file; every source file carries an SPDX header:
- Original xcx code — the public API and data-layout layer, the AD family
harnesses, the test suites, and all functionals tagged
Provenance: clean-room— is dual-licensed under MIT OR Apache-2.0 (LICENSE-MIT,LICENSE-APACHE), at your option — the standard Rust-ecosystem terms. - Code derived from libxc (tagged
Provenance: ported-from-libxc) remains under the Mozilla Public License 2.0 (LICENSE-MPL), matching upstream.
MPL-2.0 is file-level copyleft, so this scheme is permissive in practice:
you may depend on xcx from MIT/Apache/proprietary projects freely; only
modifications to the MPL-tagged source files themselves must remain under the
MPL. See NOTICE for the full scheme and attribution.
See CONTRIBUTING.md. Original contributions are made
under MIT OR Apache-2.0 (changes to libxc-derived files stay MPL-2.0); new
functionals must be verified per the Verification section.