Language bindings¶
1 Feature parity matrix¶
The five surfaces (Rust, Python, Julia, C, C++) cover the same core read/write/build functionality. The table below maps coarse features to bindings; each binding’s section below has runnable examples.
Feature |
Rust |
Python |
Julia |
C |
C++ |
|---|---|---|---|---|---|
Lazy frame iterator |
yes |
yes |
yes |
yes |
yes |
Read-all-frames helper |
|
|
|
|
|
Parallel iterator |
yes ( |
no |
no |
no |
no |
Builder API |
|
|
|
|
|
Writer API |
|
|
|
|
|
Velocity / force sections |
yes |
yes |
yes |
yes |
yes |
Per-axis fixed mask |
yes |
yes |
yes |
yes |
yes |
Typed metadata getters (energy, time, frameindex, neb*) |
yes |
yes |
yes |
yes |
yes |
Typed metadata setters (setenergy, setframeindex, …) |
yes |
yes |
yes |
yes |
yes |
Raw JSON metadata getter |
|
|
|
|
|
Strict validation ( |
yes |
yes |
yes |
yes |
yes |
RPC server |
yes ( |
no |
no |
no |
no |
Cap’n Proto serialization |
yes ( |
no |
no |
no |
no |
gzip / .gz round-trip |
yes |
yes |
yes |
yes |
yes |
zstd / .zst round-trip |
yes ( |
yes ( |
yes ( |
yes ( |
yes ( |
Per-atom |
yes |
yes |
yes |
yes |
yes |
Symbol <-> Z helpers |
yes |
derived from Atom |
yes |
|
|
|
|
|
|
|
|
Coords / forces / velocities / energies as NumPy ndarray |
n/a (use AoS) |
yes ( |
n/a |
n/a |
n/a |
metatensor |
yes ( |
n/a |
n/a |
n/a |
n/a |
2 Python (PyO3)¶
2.1 Installation¶
# From PyPI
pip install readcon
# From source with maturin
maturin develop --features python
# Or via pixi
pixi r -e python python-build
2.2 Version and spec queries¶
import readcon
print(readcon.__version__) # e.g. "0.5.0"
print(readcon.CON_SPEC_VERSION) # 2
2.3 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)
2.4 Types¶
readcon.AtomConstructable with keyword arguments (v0.4.0+). Properties: symbol, x, y, z, fixed, isfixed, atomid, mass (v0.4.2+), vx, vy, vz, hasvelocity, fx, fy, fz, hasforces, energy (v0.10.0+), hasenergy(v0.10.0+). Data fields are writable.
readcon.ConFrameConstructable with cell, angles, atoms, and optional headers and metadata (v0.4.0+). Properties: cell, angles, atoms (live list), hasvelocities, hasforces, hasenergies(v0.10.0+), preboxheader, postboxheader, specversion(v0.6.0+), metadata (v0.6.0+, live dict of native JSON-compatible values), energy, frameindex, time, timestep, nebbead, nebband. Methods: toase(), fromase() (v0.4.0+), setmetadatajson(), setscalarmetadata(), setstringmetadata(), setenergy(), setframeindex(), settime(), settimestep(), setnebbead(), setnebband(), atomindexbyid(id) (v0.10.0+), buildatomidindex() (v0.10.0+), coordsarray() (v0.10.0+), velocitiesarray() (v0.10.0+), forcesarray() (v0.10.0+), energiesarray() (v0.10.0+), atomidsarray() (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.
2.5 NumPy array views and DLPack interop (v0.10.0+)¶
Every per-atom quantity has a contiguous NumPy ndarray accessor.
Vector quantities are [N, 3] float64 arrays in the type-grouped
order used by the underlying frame; scalars are [N]. NumPy 1.22+
implements __dlpack__ on its arrays, so the returned ndarrays
interoperate zero-copy with torch / jax / cupy without readcon-core
wiring DLPack itself.
import numpy as np
import torch
import readcon
frame = readcon.read_first_frame("trajectory.con")
coords = frame.coords_array() # np.ndarray shape (N, 3) float64
forces = frame.forces_array() # Optional[np.ndarray]; None if absent
velocities = frame.velocities_array() # Optional[np.ndarray]
energies = frame.energies_array() # Optional[np.ndarray] shape (N,)
atom_ids = frame.atom_ids_array() # np.ndarray shape (N,) uint64
# Zero-copy hand-off into torch via DLPack.
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_idthrough anatom_idarray, velocities through ASE velocities, forces through aSinglePointCalculator, and per-axis fixed masks throughFixCartesian/FixAtomsconstraints.
2.6 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}')
3 Julia (ccall)¶
3.1 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
3.2 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)
3.3 Types¶
ReadCon.Atomatomicnumber, x, y, z, atomid, mass, isfixed, fixed, vx, vy, vz, hasvelocity, fx, fy, fz, hasforces
ReadCon.ConFramecell, angles, atoms, hasvelocities, hasforces, preboxheader, postboxheader, specversion, metadatajson, energy, frameindex, time, timestep, nebbead, nebband
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.
3.4 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}",
)
4 C/C++ (FFI)¶
4.1 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());
4.2 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);
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));
4.3 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);
4.4 Build system integration¶
4.4.1 Meson subproject¶
readcon = subproject('readcon-core')
readcon_dep = readcon.get_variable('readcon_dep')
executable('my_app', 'main.cpp', dependencies: readcon_dep)
4.4.2 CMake subproject¶
add_subdirectory(readcon-core)
target_link_libraries(my_app PRIVATE readcon-core::readcon-core)
5 metatensor TensorBlock export (v0.10.0+)¶
The optional metatensor Cargo feature exposes a Rust module that
builds metatensor TensorBlock instances from a frame. The feature
is default-off; enabling pulls in metatensor-core’s CMake build.
[dependencies]
readcon-core = { version = "0.10", features = ["metatensor"] }
use readcon_core::metatensor_export::{
frame_positions_block, frame_velocities_block,
frame_forces_block, frame_energies_block,
};
let frame = /* ... */;
let positions = frame_positions_block(&frame)?; // [N, 3] f64
let velocities = frame_velocities_block(&frame)?; // Option<TensorBlock>
let forces = frame_forces_block(&frame)?; // Option<TensorBlock>
let energies = frame_energies_block(&frame)?; // Option<TensorBlock>, [N, 1]
// Convenience entry point for the most common case:
let positions = frame.to_metatensor_positions_block()?;
Sample labels are atom_id (the post-grouping column-5 index from the
file); property labels are xyz (0/1/2) for vector quantities and a
single energy column for the scalar block. Users wanting a
TensorMap keyed by species can build one on top of these blocks; the
species-vs-atom-list partition is downstream-specific so the helpers
expose the building blocks rather than baking in one convention.
6 Compression formats¶
Extension |
Magic bytes |
Feature |
Reader |
Writer |
|---|---|---|---|---|
|
|
always |
transparent decode on read |
|
|
|
|
transparent decode on read |
|
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.