Tutorials¶
1 Rust¶
1.1 Reading a CON file¶
use std::fs;
use readcon_core::iterators::ConFrameIterator;
let contents = fs::read_to_string("trajectory.con").unwrap();
let iter = ConFrameIterator::new(&contents);
for result in iter {
let frame = result.unwrap();
println!("Cell: {:?}", frame.header.boxl);
println!("Atoms: {}", frame.atom_data.len());
}
1.2 Reading a convel file with velocities¶
The same iterator handles both .con and .convel files.
Velocity data is detected automatically by the parser.
use std::fs;
use readcon_core::iterators::ConFrameIterator;
let contents = fs::read_to_string("trajectory.convel").unwrap();
let iter = ConFrameIterator::new(&contents);
for result in iter {
let frame = result.unwrap();
println!("Has velocities: {}", frame.has_velocities());
for atom in &frame.atom_data {
print!("{} ({:.4}, {:.4}, {:.4})", atom.symbol, atom.x, atom.y, atom.z);
if atom.has_velocity() {
print!(" vel=({:.6}, {:.6}, {:.6})",
atom.vx.unwrap(), atom.vy.unwrap(), atom.vz.unwrap());
}
println!();
}
}
1.3 Writing frames¶
use std::fs::File;
use readcon_core::writer::ConFrameWriter;
let file = File::create("output.con").unwrap();
let mut writer = ConFrameWriter::new(file);
for frame in &frames {
writer.write_frame(frame).unwrap();
}
Velocity data is written automatically when
frame.has_velocities() returns true.
1.4 Building frames from data¶
Added in v0.4.0.
Use ConFrameBuilder to construct frames programmatically:
use readcon_core::types::ConFrameBuilder;
let mut builder = ConFrameBuilder::new([10.0, 10.0, 10.0], [90.0, 90.0, 90.0]);
builder.add_atom("Cu", 0.0, 0.0, 0.0, false, 1, 63.546);
builder.add_atom("Cu", 2.55, 2.55, 0.0, false, 2, 63.546);
let frame = builder.build();
1.5 Writing with custom precision¶
Added in v0.4.0.
Control the number of decimal places in output coordinates:
use std::fs::File;
use readcon_core::writer::ConFrameWriter;
// Default precision (6 decimal places)
let file = File::create("output.con").unwrap();
let mut writer = ConFrameWriter::new(file);
// High precision (17 decimal places for lossless roundtrip)
let file = File::create("precise.con").unwrap();
let mut writer = ConFrameWriter::with_precision(file, 17);
1.6 Reading a single frame efficiently¶
Added in v0.4.3.
When only the first frame is needed, read_first_frame stops after
parsing it. Files under 64 KiB are read into memory directly; larger
files use memory-mapped I/O.
use readcon_core::iterators::read_first_frame;
use std::path::Path;
let frame = read_first_frame(Path::new("single.con")).unwrap();
println!("Atoms: {}", frame.atom_data.len());
1.7 Reading all frames from a file¶
For trajectory files with many frames, read_all_frames applies
the same small-file optimization and uses memory-mapped I/O for
larger files.
use readcon_core::iterators::read_all_frames;
use std::path::Path;
let frames = read_all_frames(Path::new("large_trajectory.con")).unwrap();
println!("Loaded {} frames", frames.len());
1.8 Parallel parsing¶
Behind the parallel feature gate, multi-frame files can be parsed
concurrently using rayon.
#[cfg(feature = "parallel")]
use readcon_core::iterators::parse_frames_parallel;
let contents = std::fs::read_to_string("large_trajectory.con").unwrap();
let results = parse_frames_parallel(&contents);
let frames: Vec<_> = results.into_iter().filter_map(|r| r.ok()).collect();
2 Python¶
2.1 Installation¶
# From PyPI
pip install readcon
# From source
maturin develop --features python
# Via pixi
pixi r -e python python-build
2.2 Reading and inspecting frames¶
import readcon
# Read from file
frames = readcon.read_con("trajectory.con")
# Read from string
with open("trajectory.con") as f:
frames = readcon.read_con_string(f.read())
for frame in frames:
print(f"Cell: {frame.cell}")
print(f"Angles: {frame.angles}")
print(f"Atom count: {len(frame)}")
print(f"Has velocities: {frame.has_velocities}")
print(f"Pre-box header: {frame.prebox_header}")
for atom in frame.atoms:
print(f" {atom.symbol}: ({atom.x:.4f}, {atom.y:.4f}, {atom.z:.4f})")
print(f" fixed={atom.is_fixed}, id={atom.atom_id}, mass={atom.mass}")
2.3 Working with convel velocity data¶
import readcon
frames = readcon.read_con("trajectory.convel")
frame = frames[0]
if frame.has_velocities:
for atom in frame.atoms:
if atom.has_velocity:
print(f"{atom.symbol}: vel=({atom.vx:.6f}, {atom.vy:.6f}, {atom.vz:.6f})")
else:
print(f"{atom.symbol}: no velocity")
2.4 Constructing frames from data¶
Added in v0.4.0.
Build frames programmatically using Atom and ConFrame constructors:
import readcon
atoms = [
readcon.Atom(symbol="Cu", x=0.0, y=0.0, z=0.0,
is_fixed=False, atom_id=1),
readcon.Atom(symbol="Cu", x=2.55, y=2.55, z=0.0,
is_fixed=False, atom_id=2),
]
frame = readcon.ConFrame(
cell=[10.0, 10.0, 10.0],
angles=[90.0, 90.0, 90.0],
atoms=atoms,
)
2.5 Writing frames¶
import readcon
# Read, modify, and write back
frames = readcon.read_con("input.con")
# Write to file
readcon.write_con("output.con", frames)
# Write to string
output = readcon.write_con_string(frames)
print(output)
2.6 Writing with custom precision¶
Added in v0.4.0.
Pass the precision keyword to control decimal places:
import readcon
# Default (6 decimal places)
readcon.write_con("output.con", frames)
# Lossless roundtrip (17 decimal places)
readcon.write_con("precise.con", frames, precision=17)
output = readcon.write_con_string(frames, precision=17)
2.7 ASE Atoms conversion¶
Added in v0.4.0.
Convert between ConFrame and ASE Atoms objects. ASE must be
installed (it is an optional dependency).
import readcon
# ConFrame -> ase.Atoms
frames = readcon.read_con("input.con")
ase_atoms = frames[0].to_ase()
# ase.Atoms -> ConFrame
frame = readcon.ConFrame.from_ase(ase_atoms)
# Convenience: read a .con file directly as ase.Atoms list
ase_list = readcon.read_con_as_ase("trajectory.con")
2.8 Roundtrip test pattern¶
import readcon
frames = readcon.read_con("input.convel")
output = readcon.write_con_string(frames)
frames2 = readcon.read_con_string(output)
assert len(frames) == len(frames2)
for orig, reread in zip(frames, frames2):
assert len(orig) == len(reread)
assert orig.has_velocities == reread.has_velocities
3 C¶
3.1 Reading and printing frames¶
#include "readcon-core.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
CConFrameIterator *iter = read_con_file_iterator(argv[1]);
if (!iter) return 1;
RKRConFrame *handle;
while ((handle = con_frame_iterator_next(iter)) != NULL) {
CFrame *frame = rkr_frame_to_c_frame(handle);
printf("Cell: [%.4f, %.4f, %.4f]\n",
frame->cell[0], frame->cell[1], frame->cell[2]);
printf("Atoms: %zu, Has 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];
printf(" Atom %zu: (%.4f, %.4f, %.4f) fixed=%d id=%llu\n",
i, a->x, a->y, a->z, a->is_fixed,
(unsigned long long)a->atom_id);
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);
return 0;
}
3.2 Building frames from data¶
Added in v0.4.0.
#include "readcon-core.h"
double cell[3] = {10.0, 10.0, 10.0};
double angles[3] = {90.0, 90.0, 90.0};
RKRConFrame *frame = rkr_frame_new(cell, angles, "", "", "", "");
rkr_frame_add_atom(frame, "Cu", 0.0, 0.0, 0.0, false, 1, 63.546);
rkr_frame_add_atom(frame, "Cu", 2.55, 2.55, 0.0, false, 2, 63.546);
rkr_frame_finalize(frame);
// Write with custom precision
RKRConFrameWriter *writer =
create_writer_from_path_with_precision_c("output.con", 17);
rkr_writer_extend(writer, (const RKRConFrame **)&frame, 1);
free_rkr_writer(writer);
free_rkr_frame(frame);
3.3 Reading a single frame¶
Added in v0.4.0.
RKRConFrame *frame = rkr_read_first_frame("input.con");
if (frame) {
CFrame *cf = rkr_frame_to_c_frame(frame);
printf("Atoms: %zu\n", cf->num_atoms);
free_c_frame(cf);
free_rkr_frame(frame);
}
3.4 Writing frames¶
#include "readcon-core.h"
// After collecting handles into an array:
RKRConFrameWriter *writer = create_writer_from_path_c("output.con");
int result = rkr_writer_extend(writer,
(const RKRConFrame **)handles,
num_frames);
free_rkr_writer(writer);
3.5 Memory management¶
The C API uses two ownership patterns:
Opaque handles (
RKRConFrame): allocated by Rust, freed withfree_rkr_frame(). Used for lossless frame manipulation.Transparent structs (
CFrame): extracted copies freed withfree_c_frame(). Used for direct data access.
Always free both the CFrame and the RKRConFrame separately.
4 C++¶
4.1 RAII iteration with range-based for¶
#include "readcon-core.hpp"
#include <iostream>
int main(int argc, char *argv[]) {
readcon::ConFrameIterator frames(argv[1]);
for (auto&& frame : frames) {
auto cell = frame.cell();
auto angles = frame.angles();
std::cout << "Cell: " << cell[0] << ", " << cell[1] << ", "
<< cell[2] << "\n";
std::cout << "Has velocities: " << std::boolalpha
<< frame.has_velocities() << "\n";
for (const auto& atom : frame.atoms()) {
std::cout << " (" << atom.x << ", " << atom.y << ", "
<< atom.z << ")";
if (atom.has_velocity) {
std::cout << " vel=(" << atom.vx << ", " << atom.vy
<< ", " << atom.vz << ")";
}
std::cout << "\n";
}
}
return 0;
}
4.2 Building frames from data¶
Added in v0.4.0.
#include "readcon-core.hpp"
readcon::ConFrameBuilder builder(
{10.0, 10.0, 10.0}, {90.0, 90.0, 90.0});
builder.add_atom("Cu", 0.0, 0.0, 0.0, false, 1, 63.546);
builder.add_atom("Cu", 2.55, 2.55, 0.0, false, 2, 63.546);
auto frame = builder.build();
4.3 Reading a single frame¶
Added in v0.4.0.
auto frame = readcon::read_first_frame("input.con");
std::cout << "Atoms: " << frame.atoms().size() << "\n";
4.4 Collecting and writing frames¶
#include "readcon-core.hpp"
#include <vector>
readcon::ConFrameIterator input("input.con");
std::vector<readcon::ConFrame> all_frames;
for (auto&& frame : input) {
all_frames.push_back(std::move(frame));
}
// Default precision (6 decimal places)
readcon::ConFrameWriter writer("output.con");
writer.extend(all_frames);
// High precision (17 decimal places)
readcon::ConFrameWriter precise_writer("precise.con", 17);
precise_writer.extend(all_frames);
4.5 Integration with eOn¶
The C++ RAII API is used by eOn for all .con and .convel I/O:
// Reading: mmap-based single frame read
auto frame = readcon::read_first_frame(con_path);
// frame.cell(), frame.atoms(), frame.has_velocities()
// Writing: builder with precision control
readcon::ConFrameBuilder builder(lengths, angles);
for (int i = 0; i < nAtoms; i++) {
builder.add_atom(symbol, x, y, z, is_fixed, id, mass);
}
auto out_frame = builder.build();
readcon::ConFrameWriter writer(path, 17); // 17 digits for lossless roundtrip
writer.extend({out_frame});
5 Julia¶
5.1 Installation and setup¶
# Point to the shared library
ENV["READCON_LIB_PATH"] = "/path/to/libreadcon_core.so"
# Or build from source (library auto-discovered)
using Pkg
Pkg.develop(path="julia/ReadCon")
5.2 Reading frames¶
using ReadCon
frames = read_con("trajectory.con")
for frame in frames
println("Cell: ", frame.cell)
println("Angles: ", frame.angles)
println("Atoms: ", length(frame.atoms))
println("Has velocities: ", frame.has_velocities)
println("Header: ", frame.prebox_header)
for atom in frame.atoms
@printf(" (%.4f, %.4f, %.4f) fixed=%s id=%d\n",
atom.x, atom.y, atom.z, atom.is_fixed, atom.atom_id)
end
end
5.3 Working with convel velocity data¶
using ReadCon
frames = read_con("trajectory.convel")
frame = frames[1]
if frame.has_velocities
for atom in frame.atoms
if atom.has_velocity
@printf("vel=(%.6f, %.6f, %.6f)\n", atom.vx, atom.vy, atom.vz)
end
end
end
5.4 Multi-frame trajectory processing¶
using ReadCon
frames = read_con("neb_trajectory.con")
println("Loaded $(length(frames)) images")
# Process each NEB image
for (i, frame) in enumerate(frames)
n_free = count(a -> !a.is_fixed, frame.atoms)
println("Image $i: $n_free free atoms")
end