================= Language bindings ================= .. tip:: Install: :doc:`getting-started`. Rust crate API: `docs.rs/readcon-core `_ (includes :code:`index_proj`). Campaign store: `readcon-db docs `_ · `docs.rs/readcon-db `_. This page is the multi-language *reference* (parity matrix + examples). Feature parity matrix --------------------- The surfaces (Rust, Python, Julia, Fortran, C, C++) share the same core read/write/build functionality. The table below maps coarse features to bindings; multi-language panels follow for the common “read first frame” task. .. table:: +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Feature | Rust | Python | Julia | Fortran | C | C++ | +================================================================+======================================================+=====================================================+====================================================================+=======================================================+==============================================================+============================================+ | Lazy frame iterator | yes | yes (streaming ``iter_con``) | yes | ``open_iterator`` / ``iterator_t`` | yes | yes | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Read-all-frames helper | ``iterators::read_all_frames`` | ``readcon.read_all_frames`` | ``read_all_frames`` | ``rkr_read_all_frames`` | ``rkr_read_all_frames`` | ``readcon::read_all_frames`` | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Count frames (skip walk) | ``iterators::count_frames`` | ``readcon.count_frames`` | n/a | n/a | n/a | n/a | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Coordinates on a loaded frame | SoA on ``ConFrame`` | ``ConFrame.coords_array()`` (after full-frame load) | via frame fields | via frame accessors | via frame accessors | via frame accessors | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Parallel multi-frame parse | yes (``parallel``; size gate in ``read_all_frames``) | yes (via ``python~→~parallel``) | no | no | no | no | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Builder API | ``ConFrameBuilder`` | ``PyConFrameBuilder`` | ``ConFrameBuilder`` | ``rkr_frame_new`` + ``rkr_frame_add_atom_full`` | ``rkr_frame_new`` + ``rkr_frame_add_atom_full`` | ``readcon::ConFrameBuilder`` | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Writer API | ``ConFrameWriter`` | ``write_frames`` | ``write_frames`` | ``create_writer_from_path_c`` + ``rkr_writer_extend`` | ``create_writer_from_path_c`` + ``rkr_writer_extend`` | ``readcon::ConFrameWriter`` | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Velocity / force sections | yes | yes | yes | yes | yes | yes | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Per-axis fixed mask | yes | yes | yes | yes | yes | yes | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Typed metadata getters (energy, time, frame\_index, neb\_\*) | yes | yes | yes | yes | yes | yes | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Typed metadata setters (set\_energy, set\_frame\_index, ...) | yes | yes | yes | yes | yes | yes | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Raw JSON metadata getter | ``FrameHeader::metadata`` | ``PyConFrame.metadata`` | ``metadata`` | ``rkr_frame_metadata_json`` | ``rkr_frame_metadata_json`` | ``ConFrame::metadata_json`` | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Strict validation (``validate=true``) | yes | yes | yes | yes | yes | yes | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | RPC server | yes (``rpc`` feature) | no | no | no | no | no | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Cap'n Proto serialization | yes (``rpc`` feature) | no | no | no | no | no | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | gzip / .gz round-trip | yes | yes | yes | yes | yes | yes | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | zstd / .zst round-trip | yes (``zstd`` feature) | yes (``zstd`` feature) | yes (``zstd`` feature) | yes (``zstd`` feature) | yes (``zstd`` feature) | yes (``zstd`` feature) | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Per-atom ``energies`` section | yes | yes | yes | yes | yes | yes | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Symbol <-> Z helpers | yes | derived from Atom | yes | ``rkr_symbol_to_z`` / ``rkr_z_to_symbol`` | ``rkr_symbol_to_z`` / ``rkr_z_to_symbol`` | ``readcon::symbol_to_z`` / ``z_to_symbol`` | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | ``atom_id`` reverse index | ``build_atom_id_index`` | ``build_atom_id_index`` | ``build_atom_id_index`` | ``rkr_frame_atom_index_by_id`` | ``rkr_frame_atom_index_by_id`` | ``ConFrame::atom_index_by_id`` | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Coords / forces / velocities / energies as NumPy ndarray | n/a (use AoS) | yes (``numpy`` ndarray + DLPack via NumPy 1.22+) | n/a | n/a | n/a | n/a | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Builder DLPack 1.0 export (owned ``DLManagedTensorVersioned``) | yes (``dlpk``) | via NumPy | n/a | yes (all six sections + ``dlpack_inspect``) | yes (``rkr_frame_builder_*_dlpack`` + ``rkr_dlpack_delete``) | yes (same C ABI) | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | metatensor ``TensorBlock`` export | yes (``metatensor`` feature) | n/a | n/a | yes (opaque ``c_ptr``; link fat lib) | yes (gated C ABI) | yes (same C ABI) | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Optional frame ``bonds`` topology | yes | ``PyConFrame.bonds`` / ``has_bonds`` | ``metadata_json`` + ``frame_bond_count`` | ``rkr_frame_bond_*`` | ``rkr_frame_bond_*`` | ``ConFrame::bonds()`` | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ | Chemfiles import / selection | yes (``chemfiles`` feature) | ``select_on_frame`` / ``select_atom_indices`` | ``select_on_frame`` / ``select_atom_indices`` (FFI; chemfiles lib) | ``rkr_frame_select`` / ``read_chemfiles_first`` | ``rkr_frame_select`` | ``ConFrame::select`` | +----------------------------------------------------------------+------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+-------------------------------------------------------+--------------------------------------------------------------+--------------------------------------------+ **Selection (shared evaluator).** One evaluator core; every surface is a pass-through (``evaluate_selection_on_con_frame`` → chemfiles ``Selection`` after projecting the frame). Build with ``--features chemfiles``; probe with ``rkr_has_chemfiles_support()`` / ``has_chemfiles_support()`` (Julia) / feature at build time (Rust/Python). **Documentation map (Diátaxis):** core tutorial ``tutorial.org``; multi-language how-to ``howto.org``; chemfiles track ``chemfiles-tutorial.org`` / ``chemfiles-howto.org`` / ``chemfiles-explain.org`` / ``chemfiles-reference.org``. This page is reference. Release cutting: ``contributing.org`` **Release process**. Multi-language: read first frame + metadata ------------------------------------------- Same task in each user-facing language (paths are examples). .. tab-set:: .. tab-item:: Rust .. code-block:: rust use readcon_core::iterators::read_first_frame; let frame = read_first_frame(std::path::Path::new("structure.con"))?; println!("{} atoms, meta={:?}", frame.atom_data.len(), frame.header.metadata); println!("energy={:?}", frame.header.energy()); .. tab-item:: Python .. code-block:: python import readcon # Prefer first-frame / streaming / skip count when full materialization # is unnecessary (see also count_frames, iter_con). frame = readcon.read_first_frame("structure.con") print(len(frame.atoms), frame.metadata) print("energy", frame.energy) # typed accessor when present .. tab-item:: Fortran (fpm ReadCon) .. code-block:: fortran use readcon type(frame_t) :: fr type(catom_t) :: a fr = read_first_frame("structure.con") if (fr%valid()) then print *, "natoms", fr%natoms() print *, "metadata_json", fr%metadata_json() print *, "energy", fr%energy() a = fr%atom(1) print *, "atom1 fixed_x/y/z", a%fixed_x, a%fixed_y, a%fixed_z call fr%free() end if .. tab-item:: C .. code-block:: c #include "readcon-core.h" RKRConFrame *f = rkr_read_first_frame("structure.con"); CFrame *cf = rkr_frame_to_c_frame(f); char *meta = rkr_frame_metadata_json(f); printf("natoms=%zu meta=%s\n", (size_t)cf->num_atoms, meta ? meta : ""); printf("fixed_x=%d\n", cf->atoms[0].fixed_x); rkr_free_string(meta); free_c_frame(cf); free_rkr_frame(f); .. tab-item:: C++ .. code-block:: cpp #include "readcon-core.hpp" // Prefer C API or C++ wrappers in include/readcon-core.hpp auto *f = rkr_read_first_frame("structure.con"); auto *cf = rkr_frame_to_c_frame(f); char *meta = rkr_frame_metadata_json(f); // ... use cf->atoms[i].fixed_x/y/z ... rkr_free_string(meta); free_c_frame(cf); free_rkr_frame(f); **Selection on CON frames.** With ``metadata["bonds"]`` (0-based ``atom_data`` pairs): ``bonds:`` / ``angles:`` / ``dihedrals:`` / ``pairs:`` / ``two:`` / ``three:`` / ``four:``, and ``is_bonded`` / ``is_angle`` / ``is_dihedral``. Without bonds, ``name`` / ``type`` / ``all`` / ``none`` still work. Matches are always in CON ``atom_data`` order (type-grouped; import remaps bond endpoints via ``atom_id``). **Name vs type after foreign import.** On disk: one ``symbol`` column. Optional sidecars ``chemfiles_atom_names`` / ``chemfiles_atom_types`` let ``name H1`` and ``type H`` differ after conversion. Hand-built frames use ``symbol`` for both. **Format limits.** No residues, pair ``bonds`` only, thin property sidecars—so ``resname``, impropers, and most external property maps are not selectable. :doc:`chemfiles-explain`. Python (PyO3) ------------- Installation ~~~~~~~~~~~~ .. code:: shell # From PyPI pip install readcon # From source with maturin maturin develop --features python # Or via pixi pixi r -e python python-build Version and spec queries ~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python import readcon print(readcon.__version__) # e.g. "0.5.0" print(readcon.CON_SPEC_VERSION) # 2 Usage ~~~~~ .. code:: python import readcon # Read frames frames = readcon.read_con("path/to/file.con") first = readcon.read_first_frame("path/to/file.con") for frame in readcon.iter_con("path/to/file.con"): pass frames = readcon.read_con_string(contents) # Access data for frame in frames: print(frame.cell) # [f64, f64, f64] print(frame.angles) # [f64, f64, f64] print(frame.has_velocities) for atom in frame.atoms: print(atom.symbol, atom.x, atom.y, atom.z, atom.mass) if atom.has_velocity: print(atom.vx, atom.vy, atom.vz) # Construct frames (v0.4.0+) atom = readcon.Atom(symbol="Cu", x=0.0, y=0.0, z=0.0, fixed=[False, False, False], atom_id=1) frame = readcon.ConFrame(cell=[10.0, 10.0, 10.0], angles=[90.0, 90.0, 90.0], atoms=[atom]) frame.metadata["generator"] = "my-tool 1.0" frame.atoms.append(readcon.Atom(symbol="H", x=1.0, y=0.0, z=0.0)) # Write frames (with optional precision) readcon.write_con("output.con", frames) readcon.write_con("precise.con", frames, precision=17) output_str = readcon.write_con_string(frames) # ASE conversion (v0.4.0+, requires ase) ase_atoms = frame.to_ase() frame2 = readcon.ConFrame.from_ase(ase_atoms) Types ~~~~~ ``readcon.Atom`` Constructable with keyword arguments (v0.4.0+). Properties: symbol, x, y, z, fixed, is\_fixed, atom\_id, mass (v0.4.2+), vx, vy, vz, has\_velocity, fx, fy, fz, has\_forces, energy (v0.10.0+), has\_energy (v0.10.0+). Data fields are writable. ``readcon.ConFrame`` Constructable with cell, angles, atoms, and optional headers and metadata (v0.4.0+). Properties: cell, angles, atoms (live list), has\_velocities, has\_forces, has\_energies (v0.10.0+), prebox\_header, postbox\_header, spec\_version (v0.6.0+), metadata (v0.6.0+, live dict of native JSON-compatible values), energy, frame\_index, time, timestep, neb\_bead, neb\_band. Methods: to\_ase(), from\_ase() (v0.4.0+), set\_metadata\_json(), set\_scalar\_metadata(), set\_string\_metadata(), set\_energy(), set\_frame\_index(), set\_time(), set\_timestep(), set\_neb\_bead(), set\_neb\_band(), atom\_index\_by\_id(id) (v0.10.0+), build\_atom\_id\_index() (v0.10.0+), coords\_array() (v0.10.0+), velocities\_array() (v0.10.0+), forces\_array() (v0.10.0+), energies\_array() (v0.10.0+), atom\_ids\_array() (v0.10.0+). ``readcon.read_first_frame(path)`` Parse and return only the first frame. ``readcon.iter_con(path)`` Return a Python iterator over frames. The iterator API avoids indexing into ``read_con(path)`` for first-frame and loop-based workflows. NumPy array views and DLPack interop (v0.10.0+) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Every per-atom quantity has a contiguous NumPy ndarray accessor (type-grouped ``atom_data`` order). Prefer **DLPack** for cross-framework hand-off: read ``ndim``, ``shape``, and ``dtype`` (code/bits) from the tensor—do **not** assume the host language must allocate ``f64`` / ``real64`` copies. On-disk CON numerics are IEEE binary64 today, and current exports report ``kDLFloat`` / 64 for vectors and scalars (``kDLUInt`` / 64 for ``atom_id``), but consumers should branch on the tensor metadata so a future dtype change does not force a host ``f64`` ABI. C / C++ / Fortran: ``rkr_frame_*_dlpack`` / builder ``\*_dlpack`` (and ``rkr_dlpack_delete``) are the portable path. **Choose output dtype and device** with ``RKRDlpackExportOptions`` on ``\*_dlpack_ex`` — fields are DLPack ``DLDataType`` (``code`` / ``bits`` / ``lanes``) and ``DLDevice`` (``device_type`` / ``device_id``), layout-compatible with ````. On **CPU**, float sections accept the DLPack host types we can fill from CON binary64 storage: ``kDLFloat`` 32/64, ``kDLInt`` / ``kDLUInt`` 8/16/32/64, ``kDLBool`` (8-bit). Complex / bfloat / float8 / opaque / ``lanes≠1`` → validation error until implemented. Non-CPU ``device_type`` without ``--features cuda`` → ``RKR_STATUS_FEATURE_DISABLED`` (or validation error on Rust ``as_dlpack``). With optional ``cuda``, ``kDLCUDA`` / ``RKR_DL_CUDA`` allocates real device memory and frame/FFI export uses H2D for CPU-resident SoA (not host pointers labeled as CUDA). Legacy ``\*_dlpack`` exports 64-bit ``kDLFloat`` on CPU. ``rkr_frame_copy_*`` still fills ``double*``. .. code:: python import numpy as np import torch import readcon frame = readcon.read_first_frame("trajectory.con") coords = frame.coords_array() # ndarray (N, 3); inspect .dtype forces = frame.forces_array() # Optional; None if absent velocities = frame.velocities_array() energies = frame.energies_array() # Optional shape (N,) atom_ids = frame.atom_ids_array() # typically uint64 # Zero-copy hand-off into torch via DLPack (dtype follows the tensor). coords_torch = torch.from_dlpack(coords) assert coords_torch.shape == (len(frame), 3) # atom_id reverse index for O(1) lookup by file column-5 id. idx = frame.build_atom_id_index() # dict[int, int] position = idx.get(42) # Optional[int] - ASE conversion preserves ``atom_id`` through an ``atom_id`` array, velocities through ASE velocities, forces through a ``SinglePointCalculator``, and per-axis fixed masks through ``FixCartesian`` / ``FixAtoms`` constraints. Typed metadata accessors ~~~~~~~~~~~~~~~~~~~~~~~~ Every reserved JSON key has a typed setter in addition to the live ``metadata`` dict. The setters validate the input type up front so authoring with bad metadata fails immediately, while the dict path remains available for raw escape-hatch use. .. code:: python import readcon frame = readcon.read_first_frame("traj.con") # Read: typed getter returns None when absent print(frame.energy) # Optional[float] print(frame.frame_index) # Optional[int] print(frame.neb_bead) # Optional[int] # Write: typed setters validate input shape frame.set_energy(-42.5) frame.set_frame_index(7) frame.set_neb_bead(3) # Object-shaped keys still go through the dict frame.metadata["potential"] = {"type": "EMT", "cutoff": 6.0} frame.metadata["units"] = {"length": "angstrom", "energy": "eV"} # Bulk-replace metadata from a JSON string (validated against the schema) frame.set_metadata_json('{"con_spec_version": 2, "energy": -1.0}') Julia (ccall) ------------- Installation ~~~~~~~~~~~~ Set ``READCON_LIB_PATH`` to the shared library path, or build with ``cargo build --release`` and the Julia package will find it automatically. .. code:: shell export READCON_LIB_PATH=/path/to/libreadcon_core.so Usage ~~~~~ .. code:: julia using ReadCon frames = read_con("path/to/file.con") for frame in frames println(frame.cell) println(frame.angles) println(frame.has_velocities) println(frame.spec_version) println(frame.energy) for atom in frame.atoms println(atom.x, " ", atom.y, " ", atom.z) end end write_con("roundtrip.con", frames) Types ~~~~~ ``ReadCon.Atom`` atomic\_number, x, y, z, atom\_id, mass, is\_fixed, fixed, vx, vy, vz, has\_velocity, fx, fy, fz, has\_forces ``ReadCon.ConFrame`` cell, angles, atoms, has\_velocities, has\_forces, prebox\_header, postbox\_header, spec\_version, metadata\_json, energy, frame\_index, time, timestep, neb\_bead, neb\_band ``ReadCon.write_con(path, frames; precision=6)`` Writes Julia frames through the C FFI builder/writer path, preserving velocities, forces, per-axis fixed masks, atom ids, masses, and JSON metadata. Typed metadata accessors ~~~~~~~~~~~~~~~~~~~~~~~~ Mirrors the Rust and Python typed-setter helpers. Reserved keys are addressable by named getters and setters; arbitrary keys go through ``metadata_json``. .. code:: julia using ReadCon frames = read_con("traj.con") frame = first(frames) # Read: typed getters return Union{Nothing, T} println(frame.energy) # Union{Nothing, Float64} println(frame.frame_index) # Union{Nothing, UInt64} println(frame.time) # Union{Nothing, Float64} # Write: typed setters ReadCon.set_energy!(frame, -42.5) ReadCon.set_frame_index!(frame, 7) ReadCon.set_neb_bead!(frame, 3) # Bulk: replace from a JSON string (validated against the schema) ReadCon.set_metadata_json!( frame, "{\"con_spec_version\": 2, \"sections\": [\"velocities\"], \"energy\": -1.0}", ) C/C++ (FFI) ----------- Version and spec queries ~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: c #include "readcon-core.h" // Compile-time check #if RKR_CON_SPEC_VERSION < 2 #error "readcon-core spec v2 required for atom_id support" #endif // Runtime queries printf("Spec version: %u\n", rkr_con_spec_version()); printf("Library version: %s\n", rkr_library_version()); C API ~~~~~ Include ``readcon-core.h`` and link against ``libreadcon_core``. .. code:: c #include "readcon-core.h" CConFrameIterator *iter = read_con_file_iterator("file.con"); RKRConFrame *handle; while ((handle = con_frame_iterator_next(iter)) != NULL) { CFrame *frame = rkr_frame_to_c_frame(handle); printf("Atoms: %zu, 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]; 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); Path iterator + compression + in-memory buffers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``read_con_file_iterator(path)`` routes through ``compression::read_file_contents``: plain ``.con``, gzip (``.con.gz`` / magic ``1f 8b``), and zstd (``.con.zst``, requires ``--features zstd``) all work without a temp file. Prefer these when the consumer already holds text in memory (no temp file bridge): .. table:: +----------------------------------------+---------------------------------------------+---------------------------------+ | Entry | Input | Notes | +========================================+=============================================+=================================+ | ``read_con_file_iterator(path)`` | filesystem path | transparent gzip/zstd | +----------------------------------------+---------------------------------------------+---------------------------------+ | ``read_con_string_iterator(cstr)`` | null-terminated CON UTF-8 | caller-decompressed buffer | +----------------------------------------+---------------------------------------------+---------------------------------+ | ``read_con_buffer_iterator(ptr, len)`` | byte slice (not necessarily NUL-terminated) | same ownership as path iterator | +----------------------------------------+---------------------------------------------+---------------------------------+ Free with ``free_con_frame_iterator``; frames from ``con_frame_iterator_next`` with ``free_rkr_frame``. Bulk path: ``rkr_read_all_frames`` / ``free_rkr_frame_array`` or ``free_rkr_frame_ptr_array`` (outer pointer array only). Frame section buffers (no AoS required) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. table:: +--------------------------------------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------+ | C | C++ (``readcon::ConFrame``) | Meaning | +============================================================================================+=============================+=========================================================================================+ | ``rkr_frame_atom_count`` | ``atom_count()`` | ``N`` | +--------------------------------------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------+ | ``rkr_frame_copy_positions(out, 3N)`` | ``copy_positions`` | row-major xyz; always present | +--------------------------------------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------+ | ``rkr_frame_copy_velocities`` / ``forces`` / ``atom_energies`` / ``masses`` / ``atom_ids`` | matching ``copy_*`` | ``RKR_STATUS_SECTION_ABSENT`` (``-8``) if missing | +--------------------------------------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------+ | ``rkr_frame_positions_dlpack`` | ``positions_dlpack`` | owned DLPack share (dlpk ``ArcArray``); free via tensor deleter / ``rkr_dlpack_delete`` | +--------------------------------------------------------------------------------------------+-----------------------------+-----------------------------------------------------------------------------------------+ Fortran: ``fr%atom_count()``, ``fr%copy_positions(pbuf)``, ``fr%copy_velocities``, ``fr%copy_forces``, ``fr%copy_masses`` (same status codes). Builder: ``bd%copy_positions``, ``bd%copy_masses``, plus ``\*_dlpack`` helpers in ``fortran/README.md``. Multi-frame chemfiles selection (Rust + Python) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Atom-context trajectory positions (e.g. ``name H``) without re-selecting in user code: - Rust: ``evaluate_selection_on_frames`` / ``select_atom_positions_on_frames`` in ``chemfiles_selection`` (feature ``chemfiles``; lean builds return ``ChemfilesImportError::FeatureDisabled``). - Python: ``readcon.evaluate_selection_on_frames(sel, frames)`` and ``readcon.select_atom_positions_on_frames("name H", frames)`` → ``{"selection", "frames": [{"frame_index", "result", "atom_indices", "positions"}, ...]}``. Single-frame: ``select_on_frame`` / ``select_atom_indices`` / ``ConFrame.select`` / ``select_atoms`` remain. Indices are CON ``atom_data`` order (species-contiguous). No C ABI multi-frame selection entry in this cut (call per-frame ``rkr_frame_select`` or use Rust/Python). Lean builds: gated selection / metatensor return ``RKR_STATUS_FEATURE_DISABLED`` (``-11``), never confused with internal error (``-7``). Metadata builder helpers: .. code:: c RKRConFrameBuilder *builder = rkr_frame_new(cell, angles, "", "", "", ""); if (rkr_frame_builder_set_energy(builder, -42.5) != RKR_STATUS_SUCCESS) { free_rkr_frame_builder(builder); return 1; } if (rkr_frame_builder_set_frame_index(builder, 7) != RKR_STATUS_SUCCESS) { free_rkr_frame_builder(builder); return 1; } rkr_frame_builder_set_time(builder, 3.5); rkr_frame_builder_set_timestep(builder, 0.2); rkr_frame_builder_set_neb_bead(builder, 4); rkr_frame_builder_set_neb_band(builder, 1); rkr_frame_builder_set_scalar_metadata(builder, "convergence", 1.0e-3); rkr_frame_builder_set_string_metadata(builder, "generator", "eon"); rkr_frame_add_atom_with_velocity_and_forces_fixed_mask( builder, "Cu", 0.0, 0.0, 0.0, true, false, true, 0, 63.546, 0.1, 0.2, 0.3, -0.1, -0.2, -0.3); printf("status: %s\n", rkr_status_message(RKR_STATUS_SUCCESS)); C++ API ~~~~~~~ Include ``readcon-core.hpp`` for RAII wrappers. .. code:: cpp #include "readcon-core.hpp" readcon::ConFrameIterator frames("file.con"); for (auto&& frame : frames) { auto& cell = frame.cell(); auto& atoms = frame.atoms(); bool has_vel = frame.has_velocities(); for (const auto& atom : atoms) { if (atom.has_velocity) { std::cout << atom.vx << " " << atom.vy << " " << atom.vz << "\n"; } } } Builder metadata helpers: .. code:: cpp readcon::ConFrameBuilder builder({10.0, 10.0, 10.0}, {90.0, 90.0, 90.0}); builder.set_energy(-42.5); builder.set_frame_index(7); builder.set_time(3.5); builder.set_timestep(0.2); builder.set_neb_bead(4); builder.set_neb_band(1); builder.set_scalar_metadata("convergence", 1.0e-3); builder.set_string_metadata("generator", "eon"); builder.set_metadata_json(R"({"custom_key":"custom_value"})"); builder.add_atom_with_velocity_and_forces( "Cu", 0.0, 0.0, 0.0, {true, false, true}, 0, 63.546, 0.1, 0.2, 0.3, -0.1, -0.2, -0.3); Build system integration ~~~~~~~~~~~~~~~~~~~~~~~~ Meson wrap ^^^^^^^^^^ .. code:: meson readcon_dep = dependency('readcon-core') executable('my_app', 'main.c', dependencies: readcon_dep) CMake FetchContent ^^^^^^^^^^^^^^^^^^ .. code:: cmake include(FetchContent) FetchContent_Declare( readcon-core URL https://github.com/lode-org/readcon-core/releases/download/v0.14.1/readcon-core-cxx-0.14.1.tar.gz URL_HASH SHA256= ) FetchContent_MakeAvailable(readcon-core) target_link_libraries(my_app PRIVATE readcon-core::shared) metatensor TensorBlock export (v0.10.0+; C/Fortran ABI v0.13.1+) ---------------------------------------------------------------- **Design (option A):** high-level construction in Rust (``metatensor_export`` / ``TensorBlock``); **C boundary = metatensor-sys only** (``mts_block_t *``, ``mts_block_data`` / ``mts_block_labels`` / ``mts_block_free`` from ``metatensor.h``). Single ownership transfer in Rust (``tensor_block_into_raw_mts`` → ``rkr_*`` out-param; free with ``rkr_mts_block_free`` or ``mts_block_free``, not both). We do **not** merge metatensor's cbindgen header into ``readcon-core.h``; two headers, one pointer ABI. **Lean vs fat builds** .. table:: +---------+------------------------------------------------------------+-------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------------------------+------------------------+ | Variant | Cargo | ``libreadcon_core.so`` symbols | C header | Fortran module | Link ``libmetatensor`` | +=========+============================================================+=================================================+===============================================================+===================================================================================+========================+ | Lean | default / ``chemfiles`` only | symbols **always linked** (stubs) | ``READCON_CORE_HAS_METATENSOR`` optional (capability only) | C decls always present; runtime ****``RKR_STATUS_FEATURE_DISABLED`` (``-11``)**** | no real blocks | +---------+------------------------------------------------------------+-------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------------------------+------------------------+ | Fat | ``--features metatensor`` (often ``chemfiles,metatensor``) | export all four blocks + ``rkr_mts_block_free`` | ``readcon-metatensor.h`` sets ``READCON_CORE_HAS_METATENSOR`` | full TensorBlock transfer | **yes** | +---------+------------------------------------------------------------+-------------------------------------------------+---------------------------------------------------------------+-----------------------------------------------------------------------------------+------------------------+ After a fat ``cargo build --features metatensor``, ``build.rs`` writes ``target//readcon-metatensor.env`` (``READCON_METATENSOR_INCLUDE``, ``READCON_METATENSOR_LIB_DIR``) and adds link-search / ``-lmetatensor`` / rpath. ``scripts/run_fortran_tests.sh`` **sources** that file (with a glob fallback). **Headers** .. table:: +------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+ | File | When | +================================================+==============================================================================================================================================================+ | ``include/readcon-core.h`` | Always; metatensor + zstd entry points **always declared** (lean stubs ``-11=/NULL); opaque =mts_block_t``; ``READCON_CORE_HAS_*`` documents capability only | +------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``include/readcon-metatensor.h`` | Prefer for C consumers: includes **``metatensor.h`` first**, then defines the gate and ``readcon-core.h`` | +------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ``metatensor.h`` (from env INCLUDE or install) | Values/labels/free via sys C API; **required** to interpret the pointer | +------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+ Regenerate ``readcon-core.h`` with ``scripts/regen-capi-headers.sh`` (CI ``--check``). **Status codes (C/Fortran)** .. table:: +-------------------------------------------+--------------------------------------------------------------------------------------+ | Code | Meaning | +===========================================+======================================================================================+ | ``0`` (``RKR_STATUS_SUCCESS``) | Owned non-null ``mts_block_t *`` | +-------------------------------------------+--------------------------------------------------------------------------------------+ | ``-1`` | null pointer arg | +-------------------------------------------+--------------------------------------------------------------------------------------+ | ``-11`` (``RKR_STATUS_FEATURE_DISABLED``) | API not in this build (lean Fortran metatensor; never aliases internal error ``-7``) | +-------------------------------------------+--------------------------------------------------------------------------------------+ | ``-8`` (``RKR_STATUS_SECTION_ABSENT``) | Optional velocities/forces/energies missing on frame; out null | +-------------------------------------------+--------------------------------------------------------------------------------------+ | other non-zero | internal / metatensor error | +-------------------------------------------+--------------------------------------------------------------------------------------+ **Exports** .. table:: +---------------+---------------------+-------------------------------------------------------------+----------------------------------------------+ | Quantity | Shape | C ABI | Fortran (fat / ``-DREADCON_HAS_METATENSOR``) | +===============+=====================+=============================================================+==============================================+ | positions | ``[N,3]`` | ``rkr_frame_metatensor_positions_block`` | ``frame_metatensor_positions_block`` | +---------------+---------------------+-------------------------------------------------------------+----------------------------------------------+ | velocities | ``[N,3]`` or absent | ``…_velocities_block`` | ``frame_metatensor_velocities_block`` | +---------------+---------------------+-------------------------------------------------------------+----------------------------------------------+ | forces | ``[N,3]`` or absent | ``…_forces_block`` | ``frame_metatensor_forces_block`` | +---------------+---------------------+-------------------------------------------------------------+----------------------------------------------+ | atom energies | ``[N,1]`` or absent | ``…_atom_energies_block`` | ``frame_metatensor_atom_energies_block`` | +---------------+---------------------+-------------------------------------------------------------+----------------------------------------------+ | free | — | ``rkr_mts_block_free`` **or** ``mts_block_free`` (not both) | ``mts_block_free_rkr`` | +---------------+---------------------+-------------------------------------------------------------+----------------------------------------------+ Sample labels ``atom_id``; properties ``xyz`` (0/1/2) or single ``energy``. No full ``TensorMap`` keyed by species (callers build that). Example C consumer: ``examples/c_metatensor_sample.c``. .. code:: toml [dependencies] readcon-core = { version = "0.13", features = ["metatensor"] } .. code:: rust use readcon_core::metatensor_export::{ frame_positions_block, frame_velocities_block, frame_forces_block, frame_energies_block, tensor_block_into_raw_mts, // FFI transfer; prefer rkr_* from C }; let frame = /* ... */; let positions = frame_positions_block(&frame)?; // [N, 3] f64 let velocities = frame_velocities_block(&frame)?; // Option // C path: tensor_block_into_raw_mts(block) then mts_block_free only .. code:: bash cargo build --release --features chemfiles,metatensor set -a && source target/release/readcon-metatensor.env && set +a gcc -I include -I "$READCON_METATENSOR_INCLUDE" examples/c_metatensor_sample.c \ -L target/release -L "$READCON_METATENSOR_LIB_DIR" \ -Wl,-rpath,"$READCON_METATENSOR_LIB_DIR" -Wl,-rpath,$PWD/target/release \ -lreadcon_core -lmetatensor -o /tmp/c_mts .. code:: c #include "readcon-metatensor.h" /* rkr_frame_metatensor_positions_block(frame, &block); mts_block_data(block, &array); mts_block_labels(block, 0); rkr_mts_block_free(block); */ **CI / tests:** Rust ``metatensor_`` lib tests (C ABI + transfer). Fortran workflow runs lean (``chemfiles``) and fat (``chemfiles,metatensor``) via ``scripts/run_fortran_tests.sh``. Fortran suite does **not** call chemfiles C++ on CI (SIGFPE under gfortran traps); chemfiles stays on Rust CI. Python/Julia: no first-class TensorBlock helpers (matrix ``n/a``); use C or Rust. Builder and frame DLPack export (C / Fortran) --------------------------------------------- Owned ``DLManagedTensorVersioned`` tensors (mirror of ``dlpack.h``). Fortran binds struct fields for ``dlpack_inspect`` (ndim, shape, **dtype bits**) and ``dlpack_data_ptr``. \*Inspect dtype before casting the data pointer\*—do not require host ``real64`` solely because CON text uses binary64; use the bits field (64 for float sections today; 64 uint for atom ids). .. table:: +---------------------+-------------------------------------------------------------------------+-----------------------------+ | Section | C (builder / frame) | Fortran | +=====================+=========================================================================+=============================+ | positions ``[N,3]`` | ``rkr_frame_builder_positions_dlpack`` / ``rkr_frame_positions_dlpack`` | ``bd%positions_dlpack`` | +---------------------+-------------------------------------------------------------------------+-----------------------------+ | velocities | ``…_velocities_dlpack`` (frame + builder) | ``bd%velocities_dlpack`` | +---------------------+-------------------------------------------------------------------------+-----------------------------+ | forces | ``…_forces_dlpack`` | ``bd%forces_dlpack`` | +---------------------+-------------------------------------------------------------------------+-----------------------------+ | atom energies | ``…_atom_energies_dlpack`` | ``bd%atom_energies_dlpack`` | +---------------------+-------------------------------------------------------------------------+-----------------------------+ | masses ``[N]`` | ``…_masses_dlpack`` (builder) | ``bd%masses_dlpack`` | +---------------------+-------------------------------------------------------------------------+-----------------------------+ | atom ids ``[N]`` | ``…_atom_ids_dlpack`` (builder; uint) | ``bd%atom_ids_dlpack`` | +---------------------+-------------------------------------------------------------------------+-----------------------------+ | free | ``rkr_dlpack_delete`` | ``bd%dlpack_delete`` | +---------------------+-------------------------------------------------------------------------+-----------------------------+ Absent optional sections return ``RKR_STATUS_SECTION_ABSENT``. Builder exports are owned clones, not live views of internal storage. Frame ``copy_*`` helpers still take ``double*`` for hosts that already use f64; prefer DLPack when interoperating. Compression formats ------------------- .. table:: +--------------+-----------------+------------------------+----------------------------+-------------------------------------+ | Extension | Magic bytes | Feature | Reader | Writer | +==============+=================+========================+============================+=====================================+ | ``.con.gz`` | ``1f 8b`` | always | transparent decode on read | ``from_path_gzip(_with_precision)`` | +--------------+-----------------+------------------------+----------------------------+-------------------------------------+ | ``.con.zst`` | ``28 b5 2f fd`` | ``zstd`` (default-off) | transparent decode on read | ``from_path_zstd(_with_precision)`` | +--------------+-----------------+------------------------+----------------------------+-------------------------------------+ Builds without the ``zstd`` feature still detect zstd magic bytes on read and return ``io::ErrorKind::Unsupported`` pointing at the feature flag, so consumers never see a corrupt parse on a zstd file produced by another tool. Fortran (fpm ReadCon, ISO\_C\_BINDING) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Wrappers in ``fortran/ReadCon/src/readcon.f90`` over ``include/readcon-core.h`` (issue #6). Link ``libreadcon_core`` (Meson wrap, CMake FetchContent / ``find_package``, or ``pkg-config --libs readcon-core``). .. code:: bash # Lean (chemfiles only): metatensor Fortran helpers return RKR_STATUS_FEATURE_DISABLED (-11) READCON_FORTRAN_FEATURES=chemfiles scripts/run_fortran_tests.sh # Fat: real mts_block_t* path (-cpp -DREADCON_HAS_METATENSOR + libmetatensor) READCON_FORTRAN_FEATURES=chemfiles,metatensor scripts/run_fortran_tests.sh .. code:: fortran use readcon use, intrinsic :: iso_c_binding type(frame_t) :: fr type(builder_t) :: bd type(c_ptr) :: tensor, block integer :: st, ndim, bits integer(int64) :: s0, s1 logical :: ok fr = read_first_frame("structure.con") st = bd%positions_dlpack(tensor) call dlpack_inspect(tensor, ndim, s0, s1, bits, ok) call bd%dlpack_delete(tensor) st = frame_metatensor_positions_block(fr, block) ! fat lib call mts_block_free_rkr(block) call fr%free() Per-axis ``fixed_x`` / ``fixed_y`` / ``fixed_z`` are on ``catom_t`` (issue #19). Full API: ``frame_t``, ``iterator_t``, ``builder_t``, ``writer_t``, all six DLPack exports, four metatensor block exports (when linked), ``symbol_to_z`` / ``z_to_symbol``, ``has_chemfiles_support``. Details: ``fortran/README.md``. CI: ``.github/workflows/ci_fortran.yml`` (lean and fat jobs).