CON file I/O via readcon-core¶
Starting from v2.12, eOn uses the readcon-core
Rust library for .con and .convel file I/O in the C++ client, while
Python uses the companion readcon package.
C++ Client¶
Reading¶
auto frame = readcon::read_first_frame(filename);
matter.con2matter(frame);
read_first_frame uses memory-mapped I/O for efficient parsing. The returned
ConFrame is then used to populate the Matter object via the
con2matter(const readcon::ConFrame&) overload.
Writing¶
readcon::ConFrameBuilder builder(lengths, angles, prebox_header, postbox_header);
for (long i = 0; i < nAtoms; i++) {
builder.add_atom(symbol, x, y, z, is_fixed, atom_id, mass);
}
auto frame = builder.build();
readcon::ConFrameWriter writer(filename, precision);
writer.extend({frame});
Position precision is 17 digits (matching the previous %22.17f format).
Velocity files use 6-digit precision.
Movie-like outputs can also carry structured frame metadata:
eonc::io::ConFrameMetadata metadata;
metadata.frame_index = 7;
metadata.energy = matter.getPotentialEnergy();
metadata.scalars.push_back({"step_size", step_size});
metadata.scalars.push_back({"convergence", convergence});
matter.matter2con("minimization.con", /*append=*/true, &metadata);
The metadata maps onto readcon-core fields such as energy,
frame_index, neb_bead, neb_band, arbitrary scalar metadata, string
metadata, and an escape hatch for raw JSON.
Multi-frame (append)¶
For multi-frame output, append=true extends the file in place:
if (!path[i]->matter2con(filename, /*append=*/i > 0, &metadata)) {
throw std::runtime_error("Failed to append frame");
}
The new frame is serialized on its own and its bytes are concatenated onto the
target, so an N-frame trajectory costs N frame writes rather than N(N+1)/2.
ConFrameWriter emits self-contained frames with no file-level preamble, which
makes the concatenated result identical to the same frames written in one call.
Serialization goes through a scratch file next to the target because
readcon-core exposes neither a writer over a caller-owned stream nor a flush on
an open writer; the target itself is opened in append mode and flushed before
the call returns, so every intermediate state on disk is a complete multi-frame
.con that a reader or a tail can parse.
If the target file exists but cannot be parsed, the append fails rather than
truncating the history. That check runs when eOn did not write the file, or
when its size or modification time moved since eOn wrote it; frames this
process wrote and nobody touched since are extended without a re-parse.
eonc::io::resetConAppendState() drops that bookkeeping for a file replaced
with same-sized content inside one filesystem timestamp tick.
Gzip and zstd targets keep the read-all-and-rewrite path. A compressed member cannot be extended in place, and readcon-core reads a single gzip member, so appended members would be invisible on read-back.
NEB path writers use a small helper wrapper so that per-image metadata stays
consistent across neb.con, neb_path_*.con, and related outputs:
eonc::neb::writePathCon(path, tangent, eigenmode_solvers, numImages,
estimateEigenvalues, "neb.con");
That helper currently stores per-frame fields such as neb_bead, optional
neb_band, reaction_coordinate, relative_energy, and parallel_force.
Python server¶
The eon/fileio.py module uses the readcon PyPI package:
import readcon
frames = readcon.read_con(filename) # read all frames
frame = readcon.read_first_frame(filename) # single frame
readcon.write_con(filename, [frame]) # write
At the time of writing, the Python orchestration layer is still primarily a
consumer of .con data; the richer frame metadata is currently produced in
the C++ client for trajectory outputs and downstream visualization tooling.
Build requirements¶
Rust >= 1.88 (provided by pixi via conda-forge)
cbindgen >= 0.29 (installed via
cargo install cbindgen)Meson >= 1.8.0
The readcon-core library is fetched as a Meson subproject via
subprojects/readcon-core.wrap and linked statically. The wrap pins release
the 0.14 line (spec 3, x-only decode from readcon-core #25), and
client/meson.build asks for >=0.14.2 on both resolution paths.
eon/fileio.py needs the companion readcon package at >=0.14.5.
Updating the subproject¶
Meson uses an existing subprojects/readcon-core/ directory unchanged and does
not re-fetch it when the wrap revision changes. After pulling a commit that
moves the wrap, reset the checkout:
meson subprojects update --reset readcon-core
Skipping the reset leaves the build configuring against whatever tag sits on
disk. The version constraint on the subproject() call catches that at
configure time and names readcon-core, so a version complaint about the
subproject points at a stale checkout rather than at anything in
client/ConFileIO.cpp.