Architecture Overview¶
rgpot follows a metatensor-style layered architecture: a Rust core defines
the fundamental types and RPC infrastructure, exposed via a C ABI (generated by
cbindgen), with hand-written C++ RAII wrappers on top.
Existing C++ potentials remain callable through an FFI callback mechanism.
Layer Diagram¶
+---------------------------------------------+
| C++ Consumer Layer (RAII wrappers) |
| include/rgpot/*.hpp |
+---------------------------------------------+
| C ABI (generated by cbindgen) |
| rgpot-core/include/rgpot.h |
+---------------------------------------------+
| Rust Core |
| rgpot-core/src/ |
| - Types (ForceInput, ForceOut) |
| - Potential trait + callback dispatch |
| - Cap'n Proto RPC client/server |
| - Error handling (status codes + TLS msg) |
+---------------------------------------------+
| FFI callbacks
+---------------------------------------------+
| C++ Potentials |
| LJ, CuH2, XTBPot, TBLitePot, MetatomicPot |
+---------------------------------------------+
Layers in Detail¶
Rust Core (rgpot-core/)¶
The Rust crate is the single source of truth for the public API. It defines:
rgpot_force_input_tandrgpot_force_out_t#[repr(C)]structs mirroring
the legacy C++ ForceInput / ForceOut from ForceStructs.hpp.
These are the canonical data exchange types across the FFI boundary.
rgpot_status_tAn enum of status codes (
RGPOT_SUCCESS,
RGPOT_INVALID_PARAMETER, RGPOT_INTERNAL_ERROR, RGPOT_RPC_ERROR,
RGPOT_BUFFER_SIZE_ERROR).
Every extern "C" function returns one of these.
rgpot_potential_tAn opaque handle wrapping a C function pointer callback
plus a void* user_data.
This is how C++ potentials are registered into the Rust core without the core
knowing the concrete type.
rgpot_last_error()Thread-local last error message.
On any non-success return, the caller can retrieve a human-readable string describing the failure.
catch_unwindwrapperEvery
extern "C"function boundary catches Rust
panics and converts them to RGPOT_INTERNAL_ERROR plus an error message.
This prevents undefined behaviour from unwinding across the FFI boundary.
Feature Gates¶
The crate uses Cargo feature flags to keep the core minimal:
rpcEnables Cap’n Proto RPC client and server.
Adds dependencies on capnp, capnp-rpc, and tokio.
The RPC module reuses the existing Potentials.capnp schema shared with the C+
server.
cacheReserved for future caching integration.
gen-headerEnables C header generation via
cbindgen.
Used only during development; run pixi r gen-header to regenerate include/ rgpot.h.
Cap’n Proto Schema Sharing¶
The Cap’n Proto schema (Potentials.capnp) is bundled inside the Rust crate at
rgpot-core/schema/Potentials.capnp.
This is the canonical copy used when building from crates.io.
In the monorepo, build.rs also checks CppCore/rgpot/rpc/Potentials.capnp as
a fallback.
The Cap’n Proto wire format is stable across language implementations, so Rust
clients and C++ servers (or vice versa) are fully interoperable.
C ABI (rgpot-core/include/rgpot.h)¶
The C header is auto-generated by cbindgen from the Rust source.
Run pixi r gen-header (which invokes cargo build --features gen-header) to
regenerate it.
This header is the contract between the Rust core and all C/C++ consumers.
It contains:
Type definitions for all
#[repr(C)]structs and enums.Forward declarations for opaque handles (
rgpot_potential_t,
rgpot_rpc_client_t).
Function declarations for the entire public API.
Doxygen-style documentation extracted from Rust doc comments.
#ifdef RGPOT_HAS_RPCguards around RPC-specific declarations.
This header is never edited by hand; any changes to the public API are made in the Rust source and regenerated.
C++ RAII Wrappers (include/rgpot/)¶
Hand-written C++ headers provide idiomatic wrappers around the C API:
errors.hppMaps
rgpot_status_tcodes torgpot::Errorexceptions via
details::check_status().
Internally calls rgpot_last_error() to populate the exception message.
types.hppProvides
rgpot::InputSpec(a lightweight view over borrowed
input data) and rgpot::CalcResult (owns the force buffer and stores energy/
variance).
These are distinct from the legacy rgpot::ForceInput / rgpot::ForceOut types
in ForceStructs.hpp to avoid name collisions.
potential.hppThe
rgpot::PotentialHandleclass is a move-only RAII
wrapper around rgpot_potential_t*.
Key methods:
calculate(const InputSpec&)Invokes the potential and returns a
CalcResult.
Throws rgpot::Error on failure.
from_impl<Impl>(impl)Template factory that wraps any C++ potential object
with a compatible forceImpl method.
Generates a type-safe trampoline function automatically.
from_callback(fn, user_data, free_fn)Low-level factory taking an explicit
C function pointer.
rpc_client.hppRAII wrapper around
rgpot_rpc_client_t*.
Connects to a remote server on construction and provides calculate() with the
same InputSpec / CalcResult interface.
Guarded by #ifdef RGPOT_HAS_RPC.
rgpot.hppAggregate header that includes everything.
C++ Potentials¶
The CppCore/rgpot/ directory contains all potential implementations using the
CRTP-based Potential<Derived> template.
Each potential provides void forceImpl(const ForceInput&, ForceOut*) const.
To use them through the Rust core, pass them to
PotentialHandle::from_impl<>().
Built-in Potentials¶
LJPotStandard 12-6 Lennard-Jones (always available).
LJClusterPot12-6 Lennard-Jones on free boundaries, for isolated
clusters; the cell is ignored.
Configurable via LJClusterConfig (u0, cutoff, psi).
MorsePotPairwise Morse with a shifted cutoff, platinum parameters by
default.
Configurable via MorseConfig (De, a, re, cutoff).
ZBLPotScreened nuclear repulsion (Ziegler-Biersack-Littmark) with the
LAMMPS switching function, on free boundaries.
Configurable via ZBLConfig (cut_inner, cut_global); the constructor throws
std::invalid_argument unless 0 < cut_inner < cut_global.
rgpot::fortranpots::CuH2PotCopper-Hydrogen EAM.
One of the Fortran 2018 kernels under CppCore/rgpot/fortran/, declared in
FortranPots.hpp and built when with_fortran_pots finds a Fortran compiler.
Feature-gated Potentials¶
XTBPot(-Dwith_xtb=true)GFN tight-binding methods (GFNFF, GFN0-xTB,
GFN1-xTB, GFN2-xTB) via the xtb C API.
Configurable via XTBConfig struct (method, accuracy, electronic temperature,
charge, spin).
Not thread-safe on a single instance; create separate instances for parallel
use.
TBLitePot(-Dwith_tblite=true)GFN tight-binding methods (GFN1, GFN2,
IPEA1) via the tblite C API.
Same config pattern as XTBPot.
Lighter weight than xtb but fewer methods.
MetatomicPot(-Dwith_metatomic=true)Pre-trained ML atomistic models
via metatomic/PyTorch, loaded
directly as TorchScript (.pt) in C++ with no Python at evaluation time.
Uses vesin 0.5+ for neighbor lists and
PyTorch autograd for forces. Pass CPU as VesinDevice{VesinCPU, 0} (struct { VesinDeviceKind type; int device_id; }; VesinCPU alone is only the kind
enum). Thread-safe via internal mutex; caches the atomic-types tensor across
geometry steps. Configurable device (CPU/CUDA), dtype override, length unit,
consistency checks, and torch_determinism via MetatomicConfig.
TorchDeterminismPolicy::Fast (default) does not touch process-global LibTorch
state.
Strict calls apply_torch_determinism_policy, which sets
at::globalContext() deterministic algorithms (no warn-only fallback), pins
scaled-dot-product attention to the math SDP backend (flash, memory-efficient,
and cuDNN SDP off), disables TF32 for cuBLAS and cuDNN (full fp32 mantissas on
Ampere+), forces deterministic cuDNN algorithms with benchmarking off, and fills
uninitialized memory deterministically.
Those flags are process-global: they affect every Torch user in the process, and
Fast does not restore a previous Strict setting.
CUDA host processes that need bit-stable cuBLAS must also export
CUBLAS_WORKSPACE_CONFIG:4096:8= (or :16:8) before the first cuBLAS call
— that is process environment, not at::globalContext() (see NVIDIA cuBLAS
reproducibility and alertCuBLASConfigNotDeterministic). On the RPC server pass
Metatomic:/path/to/model.pt. Build with the metatomicbld pixi env (linux-64;
pulls torch, metatomic-torch, and vesin >=0.5.2,<0.6 from PyPI).
CI job potentials_metatomic in ci- orchestrator.yml.
The pip product (rgpot on PyPI) does not link Metatomic into the Python
extension.
It dlopen=s a portable =libmetatomic_engine.so chosen from rgpot/lib/torch- X.Y/ for the installed torch major.
Supported torch majors for bundled engines start at 2.7 (wheels ship 2.7–
2.13).
See:
All feature-gated potentials are detected via pkg-config / cmake and
controlled by Meson options.
NWChemPot (rgpot/NWChemPot/)¶
Always-built frontend (NWChemPot.cc) that serializes NWChemParams and
dlopen=s =libnwchemc (via DynLib.hpp).
Stable consumer C-ABI contract in nwchem_c_abi.h uses nwchemc_* symbols and
flat Cap’n Proto message bytes; nwchem_c_abi_stub.c is a no-op ABI linked by
the conformance test only. rgpot is a pure consumer: the engine implementation
(NWChem embed + Fortran) lives in the split https://github.com/OmniPotentRPC/ nwchemc project, not in rgpot.
RPC schema adds NWChemParams, PotentialConfig, and configure @1 on
Potential; potserv <port> NWChem.
Runtime: NWCHEMC_LIBRARY, RGPOT_NWCHEMC_ENGINE, NWCHEM_TOP.
Details: CppCore/rgpot/NWChemPot/README.md.
Units (rgpot/units.hpp)¶
rgpot’s internal unit system is energy = eV, length = Angstrom, force = eV/Angstrom.
Compile-time constants (CODATA 2018):
BOHR_TO_ANGSTROM,HARTREE_TO_EV,
HARTREE_BOHR_TO_EV_ANGSTROM, NEG_GRAD_TO_FORCE, Boltzmann constants, etc.
Used by XTBPot/TBLitePot (Bohr/Hartree backends) and any caller that wants zero-
overhead conversion.
Runtime expression parser (
unit_conversion_factor(from, to)): Shunting-Yard
algorithm with SI dimensional analysis, derived from metatomic-torch (PR #173).
Accepts compound expressions such as "kJ/mol", "eV/angstrom^3", or
"(eV*u)^(1/2)".
Throws std::invalid_argument on dimension mismatch or unknown tokens.
validate_unit(quantity, unit) checks a unit against a known quantity
(length, energy, force, pressure, momentum, mass, velocity,
charge).
RPC negotiation: Cap’n Proto
ForceInputcarries optionallengthUnitand
energyUnit (default "angstrom" / "eV").
The server converts inbound positions/box into Angstrom before forceImpl, then
converts energy and forces into the caller’s requested units on the response.
Error Handling Conventions¶
The project uses a two-tier error strategy:
Across the FFI boundary :: Status codes (
rgpot_status_t) plus a thread-local
error message (rgpot_last_error()).
No exceptions cross FFI.
Every Rust extern "C" function wraps its body in catch_unwind to convert
panics to RGPOT_INTERNAL_ERROR.
Within C++ :: The RAII wrappers call
details::check_status()which maps non-
success codes to rgpot::Error exceptions.
C++ consumers work with exceptions as usual.
Within Rust :: Standard
Resulttypes internally, converted to status codes
at the extern "C" boundary.
Adding a New Potential¶
To register a new C++ potential with the Rust core:
Implement the potential class inheriting from
Potential<MyPot>as usual.
The class must provide void forceImpl(const ForceInput&, ForceOut*) const.
Use the RAII wrapper to create a handle:
#include "rgpot/potential.hpp"
#include "my_pot/MyPot.hpp"
rgpot::MyPot my_pot;
auto handle = rgpot::PotentialHandle::from_impl(my_pot);
auto result = handle.calculate(input_spec);
double energy = result.energy();
Alternatively, write a standalone
extern "C"trampoline and call
rgpot_potential_new() directly from C:
rgpot_status_t my_trampoline(void *ud, const rgpot_force_input_t *in,
rgpot_force_out_t *out) {
/* ... forward to your implementation ... */
return RGPOT_SUCCESS;
}
rgpot_potential_t *pot = rgpot_potential_new(my_trampoline, my_data, NULL);
Build System Integration¶
Meson¶
The Rust crate is built via a custom_target in meson.build, gated behind the
with_rust_core option:
meson setup bbdir -Dwith_rust_core=true
meson compile -C bbdir
Meson invokes cargo build and links the resulting librgpot_core.a into the
project.
The feature flags (rpc, cache) are forwarded from the corresponding Meson
options.
Standalone Rust¶
The crate can also be built and tested independently:
cd rgpot-core
cargo build
cargo test
cargo build --features rpc # with RPC support
Directory Reference¶
Path |
Purpose |
|---|---|
|
Rust crate configuration. |
|
capnp compilation (+ cbindgen when |
|
cbindgen configuration. |
|
Rust source (types, status, potential, capi, rpc). |
|
Auto-generated C header. |
|
Hand-written C++ RAII wrapper headers. |
|
Original C++ potentials and helpers. |
|
Bundled Cap’n Proto schema (canonical for crates.io). |
|
Shared Cap’n Proto schema (monorepo fallback). |