Python API#

Build the extension (from a checkout next to readcon-core):

cd python
maturin develop --features python
from pathlib import Path
import readcon
from readcon_db import ConCorpus

db = ConCorpus("/tmp/corpus")
ro = ConCorpus("/tmp/corpus", readonly=True)
db.append_trajectory(1, "run.con")
db.append_trajectory_str(2, Path("run.con").read_text(), source="run.con")
db.append_trajectory_frames(3, readcon.read_con("run.con"))
keys = db.select(
    traj_id=1,
    symbol="Cu",
    natoms_min=1,
    natoms_max=10_000,
    energy_min=-50.0,
    energy_max=0.0,
    require_forces=True,
)
h = db.frame_hash(1, 0)          # bytes(16)
assert db.find_by_hash(h) == (1, 0)
text = db.get_frame_text(1, 0)
raw = ConCorpus.xxh3_128(b"blob")

Optional select kwargs: exact_hash= (16-byte LE xxh3-128), energy_min / energy_max, fmax_min / fmax_max, mass_min / mass_max, volume_min / volume_max, frame_index_min / frame_index_max, charge_min / charge_max, element_exact, element_min, formula, require_forces, require_velocities, require_energy, limit.

MPI: pack on root, Bcast on the caller communicator#

bcast_packed_frame(comm, corpus_dir, traj_id, frame_idx, root=0) takes an mpi4py Comm — LAMMPS lmp.world, COMM_WORLD.Split(...), a Dup. mpi4py already called MPI_Init; the helper never does, and never names the process-wide world handle.

from mpi4py import MPI
from readcon_db import ConCorpus, bcast_packed_frame, bcast_packed_frames

# Host-owned comm. A LAMMPS Python fix passes lmp.world (or a split).
comm = MPI.COMM_WORLD.Dup()
blob = bcast_packed_frame(comm, "/scratch/corpus", traj_id=1, frame_idx=0)
xyz = ConCorpus.unpack_positions(blob)
batch = bcast_packed_frames(comm, "/scratch/corpus", [(1, 0), (1, 1), (1, 2)])
frames = ConCorpus.unpack_batch(batch)
comm.Free()

Cooked H5MD 1.1 interchange (h5py; fixed natoms; CON stays authority):

db.export_h5md(traj_id=1, path="traj.h5")

Dest must not exist. File "x"; a write failure removes the dest.

The file has /h5md version [1,1] with author/creator, particles/all/position/value of shape [T][N][3], position/step and position/time of shape [T], box/edges/value of shape [T][3][3], integer-Z species, optional particles/all/velocity [T][N][3] (dest Angstrom ps-1), and unit attributes for one engine system (like metatomic model vs engine): length Angstrom, time ps, force kJ mol-1 Angstrom-1, velocity Angstrom ps-1. CON units.length / units.time / units.energy convert through unit_conversion_factor (SI). Time is CON header.time(), or i * timestep, else the frame index, all in ps. unit_conversion_factor(from, to) and canonicalize_unit(expr) are on the Python module. Callers write aliases (A, ev, femtosecond); append_trajectory(..., units={...}), extend_trajectory(..., units={...}), ingest_directory(..., units={...}), and set_units(traj_id, {...}) store canonical names (angstrom, eV, fs) in CON metadata. append / extend stamp the incoming numbers; set_units converts stored numbers so the new label is honest. get_units(traj_id, frame_idx) returns the stored units JSON as a string. Mixed-force trajectories write a full [T][N][3] force dataset (zeros on frames without forces). Mixed-velocity trajectories write a full [T][N][3] velocity dataset the same way. Box boundary follows CON pbc (periodic when absent). author/creator/boundary attrs are fixed-length ASCII. Physical unit attrs are short H5MD strings (MDAnalysis 2.10 indexes them as dict keys).

Standalone: examples/mpi_bcast_frame.py.

Cooked SoA (RCSO)#

See docs/orgmode/cooked-soa.org. Tier is opt-in; CON text remains authority. Bindings expose cook / delete / has-valid / positions / forces / velocities.