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
+---------------------------------------------+
|  Existing C++ Potentials (LJ, CuH2)         |
|  CppCore/rgpot/LennardJones/, CuH2/         |
+---------------------------------------------+

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_t and rgpot_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_t

An 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_t

An 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_unwind wrapper

Every 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:

rpc

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

cache

Reserved for future caching integration.

gen-header

Enables 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_RPC guards 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.hpp

Maps rgpot_status_t codes to rgpot::Error exceptions via details::check_status(). Internally calls rgpot_last_error() to populate the exception message.

types.hpp

Provides 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.hpp

The rgpot::PotentialHandle class 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.hpp

RAII 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.hpp

Aggregate header that includes everything.

Existing C++ Potentials

The CppCore/rgpot/ directory contains the original potential implementations (LJPot, CuH2Pot) using the CRTP-based Potential<Derived> template. These remain fully functional and unmodified. To use them through the new API, pass them to PotentialHandle::from_impl<>(), which generates a type-safe trampoline that converts between the C ABI structs and the legacy ForceInput / ForceOut types automatically.

Error Handling Conventions

The project uses a two-tier error strategy:

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

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

  3. Within Rust :: Standard Result types internally, converted to status codes at the extern "C" boundary.

Adding a New Potential

To register a new C++ potential with the Rust core:

  1. Implement the potential class inheriting from Potential<MyPot> as usual. The class must provide void forceImpl(const ForceInput&, ForceOut*) const.

  2. 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();
  1. 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

rgpot-core/Cargo.toml

Rust crate configuration.

rgpot-core/build.rs

capnp compilation (+ cbindgen when gen-header enabled).

rgpot-core/cbindgen.toml

cbindgen configuration.

rgpot-core/src/

Rust source (types, status, potential, capi, rpc).

rgpot-core/include/rgpot.h

Auto-generated C header.

include/rgpot/

Hand-written C++ RAII wrapper headers.

CppCore/rgpot/

Original C++ potentials and helpers.

rgpot-core/schema/Potentials.capnp

Bundled Cap’n Proto schema (canonical for crates.io).

CppCore/rgpot/rpc/Potentials.capnp

Shared Cap’n Proto schema (monorepo fallback).