Library architecture¶
1 Overview¶
readcon-core is structured as an hourglass API: a Rust core with thin FFI and language-specific wrapper layers.
Python (PyO3) Julia (ccall) C++ (RAII header)
\ | /
\ | /
+------- C FFI (ffi.rs) -----+
|
Rust core library
types | parser | writer | iterators
2 Core types (types.rs)¶
FrameHeader9-line header metadata (cell, angles, atom counts, masses, specversion, metadata). The
spec_versionfield (u32) records the CON spec version from the JSON metadata line. Themetadatafield (BTreeMap<String, serde_json::Value>) holds additional key-value pairs from the JSON, preserving unrecognized keys through round-trips.AtomDatumSingle atom data (symbol, coordinates, fixed flag, atomid(original atom index before type-based reordering), optional velocities, optional forces).
ConFrameComplete frame (header + atom data vector).
ConFrameBuilderBuilder pattern for constructing frames programmatically. Accepts an optional
metadatamap; the builder always setsspec_versiontoCON_SPEC_VERSION. Exposes typed setters for common keys likeenergy,frame_index,time, etc.
Symbol strings use Arc<str> to avoid per-atom string clones
within a type block and ensure thread-safety for parallel parsing.
3 Parser (parser.rs)¶
parse_line_of_n<T>Generic whitespace-separated value parser for header lines.
parse_line_of_n_f64fast-float2 specialized parser for the coordinate/velocity hot path.
parse_line_of_range_f64Flexible parser accepting between
minandmaxcolumns, padding from defaults. Used for atom lines where column 5 (atomid) is optional.parse_frame_headerConsumes 9 header lines. Detects JSON metadata on line 2 (starts with
{) for spec v2+; non-JSON line 2 triggers legacy mode (spec_version = 1). Malformed JSON (starts with{but invalid) produces an error. Reserved metadata keys are checked against the spec schema.parse_single_frameHeader + coordinate blocks. Accepts 4-column atom lines (column 5 defaults to sequential index). In validate mode, coordinate labels, symbols, fixed flags, atom ids, cell geometry, atom counts, and masses are checked before the frame is accepted.
parse_velocity_section/parse_force_sectionOptional velocity and force blocks after coordinates. Spec-v2 files declare sections in JSON metadata; files without a
sectionskey can still auto-detect velocities by blank separator. Declared sections must be present at their declared position. If JSON metadata setsvalidatetotrue, section parser paths verify component symbols, exact labels, fixed masks, and atom ids against the coordinate blocks before attaching velocities or forces.
4 Writer (writer.rs)¶
ConFrameWriter<W: Write>Generic buffered writer.
Line 2 is always serialized as a JSON object containing
con_spec_versionplus any entries fromframe.header.metadata. The originalprebox_header[1]value is overwritten. Managed keys (con_spec_versionandsections) are regenerated by the writer; whenvalidate=true, the writer emits an emptysectionsarray for coordinate-only frames.Writes header, coordinate blocks, velocity blocks (if
frame.has_velocities()), and force blocks (ifframe.has_forces()).
5 Iterators (iterators.rs)¶
ConFrameIteratorLazy frame-by-frame parser with
next()andforward()(skip without parsing atom data).read_all_frames()Convenience function using memmap2 for large trajectory files.
parse_frames_parallel()Rayon-based parallel parsing behind the
parallelfeature gate.
6 FFI layer (ffi.rs)¶
Opaque handle pattern:
RKRConFrameOpaque Rust frame handle.
CFrame/CAtomTransparent C structs for direct data access.
Iterator lifecycle:
read_con_file_iterator->con_frame_iterator_next->rkr_frame_to_c_frame->free_c_frame->free_rkr_frame->free_con_frame_iterator.
Version and spec constants:
#define RKR_CON_SPEC_VERSION 2Compile-time spec version for
#ifguards.rkr_con_spec_version()Runtime library spec version query.
rkr_library_version()Returns library version string (e.g.
"0.5.2"). The pointer is static; do not free it.
Per-frame metadata accessors:
rkr_frame_spec_version(handle)Returns the spec version stored in a parsed frame’s header (the version the file was written with, not the library’s version).
rkr_frame_metadata_json(handle)Returns the full JSON metadata line as a heap-allocated C string. The caller must free with
rkr_free_string().rkr_frame_energy(handle),rkr_frame_time(handle), etc.Typed accessors for common metadata keys.
RKRStatusPrefixed C-compatible status codes such as
RKR_STATUS_SUCCESS,RKR_STATUS_NULL_POINTER, andRKR_STATUS_BUFFER_TOO_SMALL.rkr_status_message(status)Returns a static human-readable message for every status value.
Builder FFI functions preserve the full atom payload. Existing boolean fixed helpers remain available, while
\*_fixed_maskvariants accept per-axis fixed flags. Force and velocity+force variants call the same RustConFrameBuilderpaths as native Rust.
7 C++ wrapper (readcon-core.hpp)¶
Header-only RAII wrappers with lazy caching:
ConFrameIteratorRange-based for loop support.
ConFrameCached accessors for cell, angles, atoms, headers, velocities, forces, and metadata helpers.
ConFrameWriterRAII file writer.
ConFrameBuilderOverloads for boolean fixed flags and
std::array<bool, 3>masks, with velocity, force, and velocity+force atom constructors.
8 Python wrapper (python.rs)¶
The PyO3 layer stores ConFrame.atoms as a live Python list and
ConFrame.metadata as a live Python dict. Writers validate metadata
as JSON-compatible values and convert the live atom list into a Rust
ConFrame at write time. read_first_frame(path) uses the Rust
first-frame reader; iter_con(path) exposes a Python iterator for
loop-oriented frame processing.
ASE conversion maps all-fixed atoms to FixAtoms and partial masks to
FixCartesian, preserving atom_id through a named ASE array.
9 Julia wrapper (wrapper.jl)¶
The Julia ccall layer exposes typed frame metadata fields populated
from C FFI accessors. write_con(path, frames; precision=6)
reconstructs opaque frame handles through the C builder API and writes
them through the Rust writer, preserving velocities, forces, per-axis
fixed masks, masses, atom ids, and JSON metadata.
10 C ABI install layout (cargo-c)¶
The [package.metadata.capi.*] keys in Cargo.toml drive
cargo-c. Running
cargo cinstall --prefix=$PREFIX --libdir=$PREFIX/lib --library-type cdylib
produces:
Asset |
Source |
Install path |
|---|---|---|
C header |
|
|
C++ header |
|
|
pkg-config file |
generated by cargo-c |
|
Linux shared object |
|
|
macOS dylib |
|
|
Windows DLL + import lib |
|
|
The header is shipped pre-generated at include/readcon-core.h and the
generation = false flag in [package.metadata.capi.header] tells
cargo-c to copy it from the install assets list rather than running
cbindgen at install time. Run scripts/regen-capi-headers.sh when the
FFI surface in src/ffi.rs changes; CI verifies drift via
scripts/regen-capi-headers.sh --check.
The [package.metadata.capi.pkg_config] filename = "readcon-core"
override is load-bearing: without it cargo-c defaults the .pc filename
to the [lib] name (readcon_core), which downstream packagers (the
conda-forge feedstock in particular) check for at the hyphenated name.