xtclib
A fast, standalone Python library for reading and writing GROMACS XTC trajectory files. The coordinate compression and decompression are implemented in C and wrapped with Cython, giving performance close to the native GROMACS routines while requiring only NumPy at runtime.
Features
- Read XTC files frame-by-frame (iterator) or all-at-once into NumPy arrays.
- Write XTC files with full compression, producing output that is bit-identical to GROMACS.
- Zero runtime dependencies beyond NumPy — no GROMACS installation, no MDTraj, no MDAnalysis required.
- Handles both small systems (intrinsically disordered proteins) and large solvated systems (50 k+ atoms) correctly.
- Pure-C compression core with Cython bindings — no external shared libraries to manage.
Installation
pip install -e .
# or with uv:
uv pip install -e .
Build requirements (setuptools, Cython, numpy) are declared in
pyproject.toml and installed automatically during the build.
Quick start
Reading a trajectory
from xtclib import read_xtc
data = read_xtc("trajectory.xtc")
data["coords"] # ndarray, shape (nframes, natoms, 3), float32, in nm
data["time"] # ndarray, shape (nframes,), float32
data["step"] # ndarray, shape (nframes,), int32
data["box"] # ndarray, shape (nframes, 3, 3), float32
data["natoms"] # int
Writing a trajectory
from xtclib import write_xtc
write_xtc(
"output.xtc",
coords, # (nframes, natoms, 3) array in nm
time=times, # optional, defaults to [0, 1, 2, ...]
step=steps, # optional, defaults to [0, 1, 2, ...]
box=boxes, # optional, defaults to zeros
precision=1000.0, # optional, default 1000.0
)
Frame-by-frame iteration
For large trajectories where loading everything into memory at once
is impractical, use the XTCReader iterator:
from xtclib import XTCReader
with XTCReader("large_trajectory.xtc") as reader:
for frame in reader:
print(frame.step, frame.time, frame.coords.shape)
# frame.coords is (natoms, 3) float32
# frame.box is (3, 3) float32
Round-tripping a file
from xtclib import read_xtc, write_xtc
data = read_xtc("input.xtc")
write_xtc("copy.xtc", data["coords"],
time=data["time"], step=data["step"], box=data["box"])
The output file will be byte-identical to the input.
API reference
read_xtc(filename)
Read an entire XTC file and return a dict of NumPy arrays.
| Key | Type | Description |
|---|---|---|
"coords" |
ndarray (nframes, natoms, 3) float32 |
Atom coordinates in nm |
"time" |
ndarray (nframes,) float32 |
Simulation time (usually ps) |
"step" |
ndarray (nframes,) int32 |
Integration step number |
"box" |
ndarray (nframes, 3, 3) float32 |
Unit-cell box vectors in nm |
"natoms" |
int |
Number of atoms per frame |
write_xtc(filename, coords, time=None, step=None, box=None, precision=1000.0)
Write coordinates to an XTC file with compression.
| Parameter | Type | Default | Description |
|---|---|---|---|
filename |
str |
— | Output file path |
coords |
array-like (nframes, natoms, 3) |
— | Coordinates in nm |
time |
array-like (nframes,) |
[0, 1, 2, ...] |
Frame times |
step |
array-like (nframes,) |
[0, 1, 2, ...] |
Step numbers |
box |
array-like (nframes, 3, 3) or (3, 3) |
zeros | Box vectors |
precision |
float |
1000.0 |
Precision factor |
XTCReader(filename)
Iterator-based reader. Yields XTCFrame objects with attributes
step, time, box, coords, and natoms. Can be used as a
context manager. Also provides a read_all() method that returns the
same dict as read_xtc().
How XTC compression works
The XTC format uses a lossy compression scheme for 3-D coordinates:
-
Float → integer: each coordinate is multiplied by a precision factor (default 1000) and rounded to the nearest integer. This gives ~0.001 nm (1 pm) resolution.
-
Full-range encoding: each atom's integer coordinates are encoded relative to the bounding box (
minint/maxint) using a mixed-radix bit-packing scheme. -
Run-length delta encoding: consecutive atoms whose inter-atom distances are "small" are encoded as differences from the previous atom, using a smaller bit range. The small range adapts up or down across the trajectory to track typical bond lengths.
-
Water optimisation: within small-delta runs, the first two atoms are swapped (O-H → H-O order) to make the deltas even smaller for water molecules. This is reversed transparently on reading.
The result is typically 10–20× smaller than raw float arrays for solvated biomolecular systems.
Precision and lossy encoding
XTC is a lossy format. The maximum per-coordinate rounding error is
0.5 / precision nm:
| Precision | Max error (nm) | Max error (Å) |
|---|---|---|
| 1000 (default) | 0.0005 | 0.005 |
| 10000 | 0.00005 | 0.0005 |
| 100 | 0.005 | 0.05 |
When reading a file that was written with a given precision, read_xtc
returns the exact integer-reconstructed values — there is no
additional loss on read. A read → write → read round-trip with the
same precision produces bit-identical coordinate arrays.
Compatibility
Files written by xtclib are fully compatible with:
- GROMACS (
gmx trjconv,gmx trajcat, etc.) - MDTraj (
mdtraj.load_xtc) - MDAnalysis (
MDAnalysis.coordinates.XTC) - Any tool that reads standard XTC (magic number 1995)
This has been validated by round-trip testing: files written by xtclib
produce byte-identical coordinate arrays when read back by MDTraj.
Project structure
xtclib/
├── xtclib/
│ ├── __init__.py # Package entry point
│ ├── reader.py # XTCReader, read_xtc()
│ ├── writer.py # write_xtc()
│ ├── _xtc_core.pyx # Cython wrapper
│ ├── xdr_xtc.c # C compression/decompression
│ └── xdr_xtc.h # C header
├── tests/
│ └── test_xtclib.py # 21 tests (pytest)
├── example/
│ ├── synuclein_STARLING.xtc # 140-atom IDP, 400 frames
│ ├── synuclein_STARLING.pdb # Reference structure
│ └── lysozyme_md.xtc # 50,949-atom solvated system, 101 frames
├── setup.py
└── pyproject.toml
Running tests
python -m pytest tests/ -v
The test suite validates:
- Reading both example files (shapes, metadata)
- Exact round-trip (read → write → read) for coords, time, step, box
- Byte-identical file sizes after round-trip
- PDB cross-validation (atom count, first-frame coordinates)
- MDTraj compatibility (our written files read back identically)
- Compression of synthetic/random coordinate data
License
The C compression algorithm is based on the XTC specification as implemented in GROMACS (LGPL-2.1+).
Metadata
Release files for xtclib 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| xtclib-0.1.0.tar.gz | 184.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| xtclib-0.1.0-cp312-cp312-macosx_26_0_arm64.whl | CPython 3.12 | CPython 3.12 | macOS 26.0+ ARM64 | Details |
Total release size: 452.2 kB
Release files / xtclib-0.1.0.tar.gz
| Download URL | xtclib-0.1.0.tar.gz |
|---|---|
| Size | 184.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4b4133d8bc0488dcd4f90c26c0daec7989ab3ef0114e73cd4ac5cd8a9a557007
|
|
BLAKE2b-256 checksum How to use checksums |
df7cd74d33ef4c95ac6399e7e7e9a8f8754f02e4a2be4074e166a1036e0e20f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / xtclib-0.1.0-cp312-cp312-macosx_26_0_arm64.whl
| Download URL | xtclib-0.1.0-cp312-cp312-macosx_26_0_arm64.whl |
|---|---|
| Size | 267.5 kB |
| Tags | CPython 3.12 macOS 26.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
99a8a8f08bd8a1509fe0d160ed85646528cd9c1ff75298f55c411f85922b2614
|
|
BLAKE2b-256 checksum How to use checksums |
c0b1672243eb15778e20f0873ab7f3b0e3e2ea1daec274d1dc8c9c809a05ed34
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|