A high-performance Zig library for reading and writing GROMACS XDR trajectory files (XTC and TRR formats).
- XTC reader/writer -- Compressed coordinate trajectories with lossy 3D compression
- TRR reader/writer -- Full-precision trajectories with coordinates, velocities, and forces
- High performance -- Buffered I/O, bulk byte-swapping, batched header writes
- Zero dependencies -- Pure Zig implementation, no C bindings required
- Append mode -- Append frames to existing trajectory files with natoms validation
Add as a dependency in your build.zig.zon:
.dependencies = .{
.zxdrfile = .{
.url = "https://github.com/N283T/zxdrfile/archive/<commit>.tar.gz",
.hash = "...",
},
},
Then in your build.zig:
const zxdrfile = b.dependency("zxdrfile", .{ .target = target, .optimize = optimize });
mod.addImport("zxdrfile", zxdrfile.module("zxdrfile"));const xdrfile = @import("zxdrfile");
// XTC
var reader = try xdrfile.XtcReader.open(allocator, "trajectory.xtc");
defer reader.close();
while (true) {
var frame = reader.readFrame() catch |err| {
if (err == xdrfile.XtcError.EndOfFile) break;
return err;
};
defer frame.deinit(allocator);
// frame.step, frame.time, frame.box, frame.coords, frame.precision
}
// TRR
var reader = try xdrfile.TrrReader.open(allocator, "trajectory.trr");
defer reader.close();
while (true) {
var frame = reader.readFrame() catch |err| {
if (err == xdrfile.TrrError.EndOfFile) break;
return err;
};
defer frame.deinit(allocator);
// frame.step, frame.time, frame.lambda, frame.box
// frame.coords, frame.velocities, frame.forces (optional)
}const xdrfile = @import("zxdrfile");
// XTC (lossy compression — precision controls accuracy)
var writer = try xdrfile.XtcWriter.open(allocator, "output.xtc", natoms, .write);
defer writer.close() catch {};
try writer.writeFrame(.{
.step = 0,
.time = 0.0,
.box = box,
.coords = coords,
.precision = 1000.0, // ~0.001 nm accuracy
});
// TRR (lossless)
var writer = try xdrfile.TrrWriter.open(allocator, "output.trr", natoms, .write);
defer writer.close() catch {};
try writer.writeFrame(.{
.step = 0,
.time = 0.0,
.lambda = 0.0,
.box = box,
.has_x = true,
.has_v = false,
.has_f = false,
.coords = coords,
.velocities = null,
.forces = null,
});
// Append to existing file
var writer = try xdrfile.TrrWriter.open(allocator, "existing.trr", natoms, .append);zig build test # Run unit tests
zig build validate # Run validation tests (against mdtraj reference)
zig build cross-format # Run cross-format conversion tests
zig build bench # Run benchmarks (ReleaseFast)- Zig 0.15.2 or later
Note: Zig has not yet reached 1.0 and its standard library API changes frequently between versions. This library may not compile on versions other than the one specified above. Check the CI status badge for current compatibility.
Benchmarked on Apple M4 Pro, reading all frames from trajectory files (ReleaseFast).
C reference uses the original xdrfile library from mdtraj, compiled with -O2.
| File | Atoms | Frames | Size | zxdrfile | C (mdtraj) | Speedup |
|---|---|---|---|---|---|---|
| 3tvj_I | 531 | 1,001 | 2.4 MB | 113 MB/s | 184 MB/s | 0.6x |
| 5wvo_C | 3,858 | 1,001 | 17 MB | 261 MB/s | 246 MB/s | 1.1x |
| 6sup_A | 33,377 | 1,001 | 148 MB | 321 MB/s | 284 MB/s | 1.1x |
XTC performance is comparable to C. The compression algorithm dominates runtime, so I/O optimizations have limited impact.
| File | Atoms | Frames | Size | zxdrfile | C (mdtraj) | Speedup |
|---|---|---|---|---|---|---|
| 3tvj_I | 531 | 1,001 | 6.2 MB | 1,245 MB/s | 226 MB/s | 5.5x |
| 5wvo_C | 3,858 | 1,001 | 44 MB | 5,256 MB/s | 199 MB/s | 26x |
| 6sup_A | 33,377 | 1,001 | 383 MB | 8,298 MB/s | 231 MB/s | 36x |
TRR is dramatically faster because the C library decodes each 4-byte value individually via XDR function calls, while zxdrfile reads entire vectors in bulk and byte-swaps in place.
This is not a line-by-line translation. Key differences:
- Full read/write support -- Both XTC and TRR formats support reading and writing
- Zig-native error handling -- Uses Zig's error union types instead of C-style return codes
- Allocator-aware -- All memory allocation goes through a caller-provided
std.mem.Allocator - Buffered I/O -- 64KB buffer for both reads and writes, reducing syscall overhead
- Bulk byte-swapping -- Reads/writes entire vectors at once with in-place byte-swap
- Batched header writes -- TRR header packed into a single buffer write
- Overflow-safe arithmetic -- Uses
std.math.mulfor bounds checking on atom count calculations - Compression bounds checks -- Validates
smallidxagainstFIRSTIDX/LASTIDXto prevent out-of-bounds access
This library is a Zig port of the xdrfile C library from the
mdtraj project, specifically the BSD-licensed
source files in
mdtraj/formats/xtc/src/:
xdrfile.c/xdrfile_xtc.c/xdrfile_trr.c
The original xdrfile library was created by Erik Lindahl and David van der Spoel as part of the GROMACS project, with modifications by Robert T. McGibbon for mdtraj.
Thanks to all the original authors for making this code available under the BSD 2-Clause license.
This library is based on the following projects:
- McGibbon, R. T. et al. "MDTraj: A Modern Open Library for the Analysis of Molecular Dynamics Trajectories." Biophys. J. 109, 1528–1532 (2015). doi:10.1016/j.bpj.2015.08.015
- Abraham, M. J. et al. "GROMACS: High performance molecular simulations through multi-level parallelism from laptops to supercomputers." SoftwareX 1–2, 19–25 (2015). doi:10.1016/j.softx.2015.06.001
BSD 2-Clause License. See LICENSE for details.