Language bindings

Tip

Install: Getting started. Rust crate API: docs.rs/readcon-core (includes index_proj). Campaign store: readcon-db docs · docs.rs/readcon-db. This page is the multi-language reference (parity matrix + examples).

Feature parity matrix

The surfaces (Rust, Python, Julia, Fortran, C, C++) share the same core read/write/build functionality. The table below maps coarse features to bindings; multi-language panels follow for the common “read first frame” task.

Feature

Rust

Python

Julia

Fortran

C

C++

Lazy frame iterator

yes

yes (streaming iter_con)

yes

open_iterator / iterator_t

yes

yes

Read-all-frames helper

iterators::read_all_frames

readcon.read_all_frames

read_all_frames

rkr_read_all_frames

rkr_read_all_frames

readcon::read_all_frames

Count frames (skip walk)

iterators::count_frames

readcon.count_frames

n/a

n/a

n/a

n/a

Coordinates on a loaded frame

SoA on ConFrame

ConFrame.coords_array() (after full-frame load)

via frame fields

via frame accessors

via frame accessors

via frame accessors

Parallel multi-frame parse

yes (parallel; size gate in read_all_frames)

yes (via python~→~parallel)

no

no

no

no

Builder API

ConFrameBuilder

PyConFrameBuilder

ConFrameBuilder

rkr_frame_new + rkr_frame_add_atom_full

rkr_frame_new + rkr_frame_add_atom_full

readcon::ConFrameBuilder

Writer API

ConFrameWriter

write_frames

write_frames

create_writer_from_path_c + rkr_writer_extend

create_writer_from_path_c + rkr_writer_extend

readcon::ConFrameWriter

Velocity / force sections

yes

yes

yes

yes

yes

yes

Per-axis fixed mask

yes

yes

yes

yes

yes

yes

Typed metadata getters (energy, time, frame_index, neb_*)

yes

yes

yes

yes

yes

yes

Typed metadata setters (set_energy, set_frame_index, …)

yes

yes

yes

yes

yes

yes

Raw JSON metadata getter

FrameHeader::metadata

PyConFrame.metadata

metadata

rkr_frame_metadata_json

rkr_frame_metadata_json

ConFrame::metadata_json

Strict validation (validate=true)

yes

yes

yes

yes

yes

yes

RPC server

yes (rpc feature)

no

no

no

no

no

Cap’n Proto serialization

yes (rpc feature)

no

no

no

no

no

gzip / .gz round-trip

yes

yes

yes

yes

yes

yes

zstd / .zst round-trip

yes (zstd feature)

yes (zstd feature)

yes (zstd feature)

yes (zstd feature)

yes (zstd feature)

yes (zstd feature)

Per-atom energies section

yes

yes

yes

yes

yes

yes

Symbol <-> Z helpers

yes

derived from Atom

yes

rkr_symbol_to_z / rkr_z_to_symbol

rkr_symbol_to_z / rkr_z_to_symbol

readcon::symbol_to_z / z_to_symbol

atom_id reverse index

build_atom_id_index

build_atom_id_index

build_atom_id_index

rkr_frame_atom_index_by_id

rkr_frame_atom_index_by_id

ConFrame::atom_index_by_id

Coords / forces / velocities / energies as NumPy ndarray

n/a (use AoS)

yes (numpy ndarray + DLPack via NumPy 1.22+)

n/a

n/a

n/a

n/a

Builder DLPack 1.0 export (owned DLManagedTensorVersioned)

yes (dlpk)

via NumPy

n/a

yes (all six sections + dlpack_inspect)

yes (rkr_frame_builder_*_dlpack + rkr_dlpack_delete)

yes (same C ABI)

metatensor TensorBlock export

yes (metatensor feature)

n/a

n/a

yes (opaque c_ptr; link fat lib)

yes (gated C ABI)

yes (same C ABI)

Optional frame bonds topology

yes

PyConFrame.bonds / has_bonds

metadata_json + frame_bond_count

rkr_frame_bond_*

rkr_frame_bond_*

ConFrame::bonds()

Chemfiles import / selection

yes (chemfiles feature)

select_on_frame / select_atom_indices

select_on_frame / select_atom_indices (FFI; chemfiles lib)

rkr_frame_select / read_chemfiles_first

rkr_frame_select

ConFrame::select

Selection (shared evaluator). One evaluator core; every surface is a pass-through (evaluate_selection_on_con_frame → chemfiles Selection after projecting the frame). Build with --features chemfiles; probe with rkr_has_chemfiles_support() / has_chemfiles_support() (Julia) / feature at build time (Rust/Python).

Documentation map (Diátaxis): core tutorial tutorial.org; multi-language how-to howto.org; chemfiles track chemfiles-tutorial.org / chemfiles-howto.org / chemfiles-explain.org / chemfiles-reference.org. This page is reference. Release cutting: contributing.org Release process.

Multi-language: read first frame + metadata

Same task in each user-facing language (paths are examples).

use readcon_core::iterators::read_first_frame;
let frame = read_first_frame(std::path::Path::new("structure.con"))?;
println!("{} atoms, meta={:?}", frame.atom_data.len(), frame.header.metadata);
println!("energy={:?}", frame.header.energy());
import readcon
# Prefer first-frame / streaming / skip count when full materialization
# is unnecessary (see also count_frames, iter_con).
frame = readcon.read_first_frame("structure.con")
print(len(frame.atoms), frame.metadata)
print("energy", frame.energy)  # typed accessor when present
use readcon
type(frame_t) :: fr
type(catom_t) :: a
fr = read_first_frame("structure.con")
if (fr%valid()) then
  print *, "natoms", fr%natoms()
  print *, "metadata_json", fr%metadata_json()
  print *, "energy", fr%energy()
  a = fr%atom(1)
  print *, "atom1 fixed_x/y/z", a%fixed_x, a%fixed_y, a%fixed_z
  call fr%free()
end if
#include "readcon-core.h"
RKRConFrame *f = rkr_read_first_frame("structure.con");
CFrame *cf = rkr_frame_to_c_frame(f);
char *meta = rkr_frame_metadata_json(f);
printf("natoms=%zu meta=%s\n", (size_t)cf->num_atoms, meta ? meta : "");
printf("fixed_x=%d\n", cf->atoms[0].fixed_x);
rkr_free_string(meta);
free_c_frame(cf);
free_rkr_frame(f);
#include "readcon-core.hpp"
// Prefer C API or C++ wrappers in include/readcon-core.hpp
auto *f = rkr_read_first_frame("structure.con");
auto *cf = rkr_frame_to_c_frame(f);
char *meta = rkr_frame_metadata_json(f);
// ... use cf->atoms[i].fixed_x/y/z ...
rkr_free_string(meta);
free_c_frame(cf);
free_rkr_frame(f);

Selection on CON frames. With metadata["bonds"] (0-based atom_data pairs): bonds: / angles: / dihedrals: / pairs: / two: / three: / four:, and is_bonded / is_angle / is_dihedral. Without bonds, name / type / all / none still work. Matches are always in CON atom_data order (type-grouped; import remaps bond endpoints via atom_id).

Name vs type after foreign import. On disk: one symbol column. Optional sidecars chemfiles_atom_names / chemfiles_atom_types let name H1 and type H differ after conversion. Hand-built frames use symbol for both.

Format limits. No residues, pair bonds only, thin property sidecars—so resname, impropers, and most external property maps are not selectable. Explanation — Chemfiles ingress and CON topology.

Python (PyO3)

Installation

# From PyPI
pip install readcon

# From source with maturin
maturin develop --features python

# Or via pixi
pixi r -e python python-build

Version and spec queries

import readcon

print(readcon.__version__)       # e.g. "0.5.0"
print(readcon.CON_SPEC_VERSION)  # 2

Usage

import readcon

# Read frames
frames = readcon.read_con("path/to/file.con")
first = readcon.read_first_frame("path/to/file.con")
for frame in readcon.iter_con("path/to/file.con"):
    pass
frames = readcon.read_con_string(contents)

# Access data
for frame in frames:
    print(frame.cell)           # [f64, f64, f64]
    print(frame.angles)         # [f64, f64, f64]
    print(frame.has_velocities)
    for atom in frame.atoms:
        print(atom.symbol, atom.x, atom.y, atom.z, atom.mass)
        if atom.has_velocity:
            print(atom.vx, atom.vy, atom.vz)

# Construct frames (v0.4.0+)
atom = readcon.Atom(symbol="Cu", x=0.0, y=0.0, z=0.0,
                    fixed=[False, False, False], atom_id=1)
frame = readcon.ConFrame(cell=[10.0, 10.0, 10.0],
                         angles=[90.0, 90.0, 90.0],
                         atoms=[atom])
frame.metadata["generator"] = "my-tool 1.0"
frame.atoms.append(readcon.Atom(symbol="H", x=1.0, y=0.0, z=0.0))

# Write frames (with optional precision)
readcon.write_con("output.con", frames)
readcon.write_con("precise.con", frames, precision=17)
output_str = readcon.write_con_string(frames)

# ASE conversion (v0.4.0+, requires ase)
ase_atoms = frame.to_ase()
frame2 = readcon.ConFrame.from_ase(ase_atoms)

Types

readcon.Atom

Constructable with keyword arguments (v0.4.0+). Properties: symbol, x, y, z, fixed, is_fixed, atom_id, mass (v0.4.2+), vx, vy, vz, has_velocity, fx, fy, fz, has_forces, energy (v0.10.0+), has_energy (v0.10.0+). Data fields are writable.

readcon.ConFrame

Constructable with cell, angles, atoms, and optional headers and metadata (v0.4.0+). Properties: cell, angles, atoms (live list), has_velocities, has_forces, has_energies (v0.10.0+), prebox_header, postbox_header, spec_version (v0.6.0+), metadata (v0.6.0+, live dict of native JSON-compatible values), energy, frame_index, time, timestep, neb_bead, neb_band. Methods: to_ase(), from_ase() (v0.4.0+), set_metadata_json(), set_scalar_metadata(), set_string_metadata(), set_energy(), set_frame_index(), set_time(), set_timestep(), set_neb_bead(), set_neb_band(), atom_index_by_id(id) (v0.10.0+), build_atom_id_index() (v0.10.0+), coords_array() (v0.10.0+), velocities_array() (v0.10.0+), forces_array() (v0.10.0+), energies_array() (v0.10.0+), atom_ids_array() (v0.10.0+).

readcon.read_first_frame(path)

Parse and return only the first frame.

readcon.iter_con(path)

Return a Python iterator over frames. The iterator API avoids indexing into read_con(path) for first-frame and loop-based workflows.

NumPy array views and DLPack interop (v0.10.0+)

Every per-atom quantity has a contiguous NumPy ndarray accessor (type-grouped atom_data order). Prefer DLPack for cross-framework hand-off: read ndim, shape, and dtype (code/bits) from the tensor—do not assume the host language must allocate f64 / real64 copies. On-disk CON numerics are IEEE binary64 today, and current exports report kDLFloat / 64 for vectors and scalars (kDLUInt / 64 for atom_id), but consumers should branch on the tensor metadata so a future dtype change does not force a host f64 ABI.

C / C++ / Fortran: rkr_frame_*_dlpack / builder \*_dlpack (and rkr_dlpack_delete) are the portable path. Choose output dtype and device with RKRDlpackExportOptions on \*_dlpack_ex — fields are DLPack DLDataType (code / bits / lanes) and DLDevice (device_type / device_id), layout-compatible with <dlpack/dlpack.h>.

On CPU, float sections accept the DLPack host types we can fill from CON binary64 storage: kDLFloat 32/64, kDLInt / kDLUInt 8/16/32/64, kDLBool (8-bit). Complex / bfloat / float8 / opaque / lanes≠1 → validation error until implemented. Non-CPU device_type without --features cudaRKR_STATUS_FEATURE_DISABLED (or validation error on Rust as_dlpack). With optional cuda, kDLCUDA / RKR_DL_CUDA allocates real device memory and frame/FFI export uses H2D for CPU-resident SoA (not host pointers labeled as CUDA). Legacy \*_dlpack exports 64-bit kDLFloat on CPU. rkr_frame_copy_* still fills double*.

import numpy as np
import torch
import readcon

frame = readcon.read_first_frame("trajectory.con")

coords = frame.coords_array()         # ndarray (N, 3); inspect .dtype
forces = frame.forces_array()         # Optional; None if absent
velocities = frame.velocities_array()
energies = frame.energies_array()     # Optional shape (N,)
atom_ids = frame.atom_ids_array()     # typically uint64

# Zero-copy hand-off into torch via DLPack (dtype follows the tensor).
coords_torch = torch.from_dlpack(coords)
assert coords_torch.shape == (len(frame), 3)

# atom_id reverse index for O(1) lookup by file column-5 id.
idx = frame.build_atom_id_index()      # dict[int, int]
position = idx.get(42)                 # Optional[int]
  • ASE conversion preserves atom_id through an atom_id array, velocities through ASE velocities, forces through a SinglePointCalculator, and per-axis fixed masks through FixCartesian / FixAtoms constraints.

Typed metadata accessors

Every reserved JSON key has a typed setter in addition to the live metadata dict. The setters validate the input type up front so authoring with bad metadata fails immediately, while the dict path remains available for raw escape-hatch use.

import readcon

frame = readcon.read_first_frame("traj.con")

# Read: typed getter returns None when absent
print(frame.energy)           # Optional[float]
print(frame.frame_index)      # Optional[int]
print(frame.neb_bead)         # Optional[int]

# Write: typed setters validate input shape
frame.set_energy(-42.5)
frame.set_frame_index(7)
frame.set_neb_bead(3)

# Object-shaped keys still go through the dict
frame.metadata["potential"] = {"type": "EMT", "cutoff": 6.0}
frame.metadata["units"] = {"length": "angstrom", "energy": "eV"}

# Bulk-replace metadata from a JSON string (validated against the schema)
frame.set_metadata_json('{"con_spec_version": 2, "energy": -1.0}')

Julia (ccall)

Installation

Set READCON_LIB_PATH to the shared library path, or build with cargo build --release and the Julia package will find it automatically.

export READCON_LIB_PATH=/path/to/libreadcon_core.so

Usage

using ReadCon

frames = read_con("path/to/file.con")

for frame in frames
    println(frame.cell)
    println(frame.angles)
    println(frame.has_velocities)
    println(frame.spec_version)
    println(frame.energy)
    for atom in frame.atoms
        println(atom.x, " ", atom.y, " ", atom.z)
    end
end

write_con("roundtrip.con", frames)

Types

ReadCon.Atom

atomic_number, x, y, z, atom_id, mass, is_fixed, fixed, vx, vy, vz, has_velocity, fx, fy, fz, has_forces

ReadCon.ConFrame

cell, angles, atoms, has_velocities, has_forces, prebox_header, postbox_header, spec_version, metadata_json, energy, frame_index, time, timestep, neb_bead, neb_band

ReadCon.write_con(path, frames; precision=6)

Writes Julia frames through the C FFI builder/writer path, preserving velocities, forces, per-axis fixed masks, atom ids, masses, and JSON metadata.

Typed metadata accessors

Mirrors the Rust and Python typed-setter helpers. Reserved keys are addressable by named getters and setters; arbitrary keys go through metadata_json.

using ReadCon

frames = read_con("traj.con")
frame = first(frames)

# Read: typed getters return Union{Nothing, T}
println(frame.energy)        # Union{Nothing, Float64}
println(frame.frame_index)   # Union{Nothing, UInt64}
println(frame.time)          # Union{Nothing, Float64}

# Write: typed setters
ReadCon.set_energy!(frame, -42.5)
ReadCon.set_frame_index!(frame, 7)
ReadCon.set_neb_bead!(frame, 3)

# Bulk: replace from a JSON string (validated against the schema)
ReadCon.set_metadata_json!(
    frame,
    "{\"con_spec_version\": 2, \"sections\": [\"velocities\"], \"energy\": -1.0}",
)

C/C++ (FFI)

Version and spec queries

#include "readcon-core.h"

// Compile-time check
#if RKR_CON_SPEC_VERSION < 2
#error "readcon-core spec v2 required for atom_id support"
#endif

// Runtime queries
printf("Spec version: %u\n", rkr_con_spec_version());
printf("Library version: %s\n", rkr_library_version());

C API

Include readcon-core.h and link against libreadcon_core.

#include "readcon-core.h"

CConFrameIterator *iter = read_con_file_iterator("file.con");
RKRConFrame *handle;
while ((handle = con_frame_iterator_next(iter)) != NULL) {
    CFrame *frame = rkr_frame_to_c_frame(handle);
    printf("Atoms: %zu, Velocities: %s\n",
           frame->num_atoms, frame->has_velocities ? "yes" : "no");
    for (size_t i = 0; i < frame->num_atoms; i++) {
        CAtom *a = &frame->atoms[i];
        if (a->has_velocity) {
            printf("  vel=(%.6f, %.6f, %.6f)\n", a->vx, a->vy, a->vz);
        }
    }
    free_c_frame(frame);
    free_rkr_frame(handle);
}
free_con_frame_iterator(iter);

Path iterator + compression + in-memory buffers

read_con_file_iterator(path) routes through compression::read_file_contents: plain .con, gzip (.con.gz / magic 1f 8b), and zstd (.con.zst, requires --features zstd) all work without a temp file. Prefer these when the consumer already holds text in memory (no temp file bridge):

Entry

Input

Notes

read_con_file_iterator(path)

filesystem path

transparent gzip/zstd

read_con_string_iterator(cstr)

null-terminated CON UTF-8

caller-decompressed buffer

read_con_buffer_iterator(ptr, len)

byte slice (not necessarily NUL-terminated)

same ownership as path iterator

Free with free_con_frame_iterator; frames from con_frame_iterator_next with free_rkr_frame. Bulk path: rkr_read_all_frames / free_rkr_frame_array or free_rkr_frame_ptr_array (outer pointer array only).

Frame section buffers (no AoS required)

C

C++ (readcon::ConFrame)

Meaning

rkr_frame_atom_count

atom_count()

N

rkr_frame_copy_positions(out, 3N)

copy_positions

row-major xyz; always present

rkr_frame_copy_velocities / forces / atom_energies / masses / atom_ids

matching copy_*

RKR_STATUS_SECTION_ABSENT (-8) if missing

rkr_frame_positions_dlpack

positions_dlpack

owned DLPack share (dlpk ArcArray); free via tensor deleter / rkr_dlpack_delete

Fortran: fr%atom_count(), fr%copy_positions(pbuf), fr%copy_velocities, fr%copy_forces, fr%copy_masses (same status codes). Builder: bd%copy_positions, bd%copy_masses, plus \*_dlpack helpers in fortran/README.md.

Multi-frame chemfiles selection (Rust + Python)

Atom-context trajectory positions (e.g. name H) without re-selecting in user code:

  • Rust: evaluate_selection_on_frames / select_atom_positions_on_frames in chemfiles_selection (feature chemfiles; lean builds return ChemfilesImportError::FeatureDisabled).

  • Python: readcon.evaluate_selection_on_frames(sel, frames) and readcon.select_atom_positions_on_frames("name H", frames){"selection", "frames": [{"frame_index", "result", "atom_indices", "positions"}, ...]}. Single-frame: select_on_frame / select_atom_indices / ConFrame.select / select_atoms remain. Indices are CON atom_data order (species-contiguous).

No C ABI multi-frame selection entry in this cut (call per-frame rkr_frame_select or use Rust/Python). Lean builds: gated selection / metatensor return RKR_STATUS_FEATURE_DISABLED (-11), never confused with internal error (-7).

Metadata builder helpers:

RKRConFrameBuilder *builder = rkr_frame_new(cell, angles, "", "", "", "");
if (rkr_frame_builder_set_energy(builder, -42.5) != RKR_STATUS_SUCCESS) {
    free_rkr_frame_builder(builder);
    return 1;
}
if (rkr_frame_builder_set_frame_index(builder, 7) != RKR_STATUS_SUCCESS) {
    free_rkr_frame_builder(builder);
    return 1;
}
rkr_frame_builder_set_time(builder, 3.5);
rkr_frame_builder_set_timestep(builder, 0.2);
rkr_frame_builder_set_neb_bead(builder, 4);
rkr_frame_builder_set_neb_band(builder, 1);
rkr_frame_builder_set_scalar_metadata(builder, "convergence", 1.0e-3);
rkr_frame_builder_set_string_metadata(builder, "generator", "eon");
rkr_frame_add_atom_with_velocity_and_forces_fixed_mask(
    builder, "Cu", 0.0, 0.0, 0.0,
    true, false, true,
    0, 63.546,
    0.1, 0.2, 0.3,
    -0.1, -0.2, -0.3);
printf("status: %s\n", rkr_status_message(RKR_STATUS_SUCCESS));

C++ API

Include readcon-core.hpp for RAII wrappers.

#include "readcon-core.hpp"

readcon::ConFrameIterator frames("file.con");
for (auto&& frame : frames) {
    auto& cell = frame.cell();
    auto& atoms = frame.atoms();
    bool has_vel = frame.has_velocities();
    for (const auto& atom : atoms) {
        if (atom.has_velocity) {
            std::cout << atom.vx << " " << atom.vy << " " << atom.vz << "\n";
        }
    }
}

Builder metadata helpers:

readcon::ConFrameBuilder builder({10.0, 10.0, 10.0}, {90.0, 90.0, 90.0});
builder.set_energy(-42.5);
builder.set_frame_index(7);
builder.set_time(3.5);
builder.set_timestep(0.2);
builder.set_neb_bead(4);
builder.set_neb_band(1);
builder.set_scalar_metadata("convergence", 1.0e-3);
builder.set_string_metadata("generator", "eon");
builder.set_metadata_json(R"({"custom_key":"custom_value"})");
builder.add_atom_with_velocity_and_forces(
    "Cu", 0.0, 0.0, 0.0,
    {true, false, true},
    0, 63.546,
    0.1, 0.2, 0.3,
    -0.1, -0.2, -0.3);

Build system integration

Meson wrap

readcon_dep = dependency('readcon-core')
executable('my_app', 'main.c', dependencies: readcon_dep)

CMake FetchContent

include(FetchContent)
FetchContent_Declare(
  readcon-core
  URL https://github.com/lode-org/readcon-core/releases/download/v0.14.1/readcon-core-cxx-0.14.1.tar.gz
  URL_HASH SHA256=<sha256 from the .sha256 sidecar on the GitHub Release>
)
FetchContent_MakeAvailable(readcon-core)
target_link_libraries(my_app PRIVATE readcon-core::shared)

metatensor TensorBlock export (v0.10.0+; C/Fortran ABI v0.13.1+)

Design (option A): high-level construction in Rust (metatensor_export / TensorBlock); C boundary = metatensor-sys only (mts_block_t *, mts_block_data / mts_block_labels / mts_block_free from metatensor.h). Single ownership transfer in Rust (tensor_block_into_raw_mtsrkr_* out-param; free with rkr_mts_block_free or mts_block_free, not both). We do not merge metatensor’s cbindgen header into readcon-core.h; two headers, one pointer ABI.

Lean vs fat builds

Variant

Cargo

libreadcon_core.so symbols

C header

Fortran module

Link libmetatensor

Lean

default / chemfiles only

symbols always linked (stubs)

READCON_CORE_HAS_METATENSOR optional (capability only)

C decls always present; runtime **``RKR_STATUS_FEATURE_DISABLED`` (``-11``)**

no real blocks

Fat

--features metatensor (often chemfiles,metatensor)

export all four blocks + rkr_mts_block_free

readcon-metatensor.h sets READCON_CORE_HAS_METATENSOR

full TensorBlock transfer

yes

After a fat cargo build --features metatensor, build.rs writes target/<profile>/readcon-metatensor.env (READCON_METATENSOR_INCLUDE, READCON_METATENSOR_LIB_DIR) and adds link-search / -lmetatensor / rpath. scripts/run_fortran_tests.sh sources that file (with a glob fallback).

Headers

File

When

include/readcon-core.h

Always; metatensor + zstd entry points always declared (lean stubs -11=/NULL); opaque =mts_block_t; READCON_CORE_HAS_* documents capability only

include/readcon-metatensor.h

Prefer for C consumers: includes ``metatensor.h`` first, then defines the gate and readcon-core.h

metatensor.h (from env INCLUDE or install)

Values/labels/free via sys C API; required to interpret the pointer

Regenerate readcon-core.h with scripts/regen-capi-headers.sh (CI --check).

Status codes (C/Fortran)

Code

Meaning

0 (RKR_STATUS_SUCCESS)

Owned non-null mts_block_t *

-1

null pointer arg

-11 (RKR_STATUS_FEATURE_DISABLED)

API not in this build (lean Fortran metatensor; never aliases internal error -7)

-8 (RKR_STATUS_SECTION_ABSENT)

Optional velocities/forces/energies missing on frame; out null

other non-zero

internal / metatensor error

Exports

Quantity

Shape

C ABI

Fortran (fat / -DREADCON_HAS_METATENSOR)

positions

[N,3]

rkr_frame_metatensor_positions_block

frame_metatensor_positions_block

velocities

[N,3] or absent

…_velocities_block

frame_metatensor_velocities_block

forces

[N,3] or absent

…_forces_block

frame_metatensor_forces_block

atom energies

[N,1] or absent

…_atom_energies_block

frame_metatensor_atom_energies_block

free

rkr_mts_block_free or mts_block_free (not both)

mts_block_free_rkr

Sample labels atom_id; properties xyz (0/1/2) or single energy. No full TensorMap keyed by species (callers build that). Example C consumer: examples/c_metatensor_sample.c.

[dependencies]
readcon-core = { version = "0.13", features = ["metatensor"] }
use readcon_core::metatensor_export::{
    frame_positions_block, frame_velocities_block,
    frame_forces_block, frame_energies_block,
    tensor_block_into_raw_mts, // FFI transfer; prefer rkr_* from C
};

let frame = /* ... */;
let positions = frame_positions_block(&frame)?;       // [N, 3] f64
let velocities = frame_velocities_block(&frame)?;     // Option<TensorBlock>
// C path: tensor_block_into_raw_mts(block) then mts_block_free only
cargo build --release --features chemfiles,metatensor
set -a && source target/release/readcon-metatensor.env && set +a
gcc -I include -I "$READCON_METATENSOR_INCLUDE" examples/c_metatensor_sample.c \
  -L target/release -L "$READCON_METATENSOR_LIB_DIR" \
  -Wl,-rpath,"$READCON_METATENSOR_LIB_DIR" -Wl,-rpath,$PWD/target/release \
  -lreadcon_core -lmetatensor -o /tmp/c_mts
#include "readcon-metatensor.h"
/* rkr_frame_metatensor_positions_block(frame, &block);
   mts_block_data(block, &array);  mts_block_labels(block, 0);
   rkr_mts_block_free(block); */

CI / tests: Rust metatensor_ lib tests (C ABI + transfer). Fortran workflow runs lean (chemfiles) and fat (chemfiles,metatensor) via scripts/run_fortran_tests.sh. Fortran suite does not call chemfiles C++ on CI (SIGFPE under gfortran traps); chemfiles stays on Rust CI. Python/Julia: no first-class TensorBlock helpers (matrix n/a); use C or Rust.

Builder and frame DLPack export (C / Fortran)

Owned DLManagedTensorVersioned tensors (mirror of dlpack.h). Fortran binds struct fields for dlpack_inspect (ndim, shape, dtype bits) and dlpack_data_ptr. *Inspect dtype before casting the data pointer*—do not require host real64 solely because CON text uses binary64; use the bits field (64 for float sections today; 64 uint for atom ids).

Section

C (builder / frame)

Fortran

positions [N,3]

rkr_frame_builder_positions_dlpack / rkr_frame_positions_dlpack

bd%positions_dlpack

velocities

…_velocities_dlpack (frame + builder)

bd%velocities_dlpack

forces

…_forces_dlpack

bd%forces_dlpack

atom energies

…_atom_energies_dlpack

bd%atom_energies_dlpack

masses [N]

…_masses_dlpack (builder)

bd%masses_dlpack

atom ids [N]

…_atom_ids_dlpack (builder; uint)

bd%atom_ids_dlpack

free

rkr_dlpack_delete

bd%dlpack_delete

Absent optional sections return RKR_STATUS_SECTION_ABSENT. Builder exports are owned clones, not live views of internal storage. Frame copy_* helpers still take double* for hosts that already use f64; prefer DLPack when interoperating.

Compression formats

Extension

Magic bytes

Feature

Reader

Writer

.con.gz

1f 8b

always

transparent decode on read

from_path_gzip(_with_precision)

.con.zst

28 b5 2f fd

zstd (default-off)

transparent decode on read

from_path_zstd(_with_precision)

Builds without the zstd feature still detect zstd magic bytes on read and return io::ErrorKind::Unsupported pointing at the feature flag, so consumers never see a corrupt parse on a zstd file produced by another tool.

Fortran (fpm ReadCon, ISO_C_BINDING)

Wrappers in fortran/ReadCon/src/readcon.f90 over include/readcon-core.h (issue #6). Link libreadcon_core (Meson wrap, CMake FetchContent / find_package, or pkg-config --libs readcon-core).

# Lean (chemfiles only): metatensor Fortran helpers return RKR_STATUS_FEATURE_DISABLED (-11)
READCON_FORTRAN_FEATURES=chemfiles scripts/run_fortran_tests.sh

# Fat: real mts_block_t* path (-cpp -DREADCON_HAS_METATENSOR + libmetatensor)
READCON_FORTRAN_FEATURES=chemfiles,metatensor scripts/run_fortran_tests.sh
use readcon
use, intrinsic :: iso_c_binding
type(frame_t) :: fr
type(builder_t) :: bd
type(c_ptr) :: tensor, block
integer :: st, ndim, bits
integer(int64) :: s0, s1
logical :: ok
fr = read_first_frame("structure.con")
st = bd%positions_dlpack(tensor)
call dlpack_inspect(tensor, ndim, s0, s1, bits, ok)
call bd%dlpack_delete(tensor)
st = frame_metatensor_positions_block(fr, block)  ! fat lib
call mts_block_free_rkr(block)
call fr%free()

Per-axis fixed_x / fixed_y / fixed_z are on catom_t (issue #19). Full API: frame_t, iterator_t, builder_t, writer_t, all six DLPack exports, four metatensor block exports (when linked), symbol_to_z / z_to_symbol, has_chemfiles_support. Details: fortran/README.md. CI: .github/workflows/ci_fortran.yml (lean and fat jobs).