#+title: Library architecture
#+options: toc:2 num:t

* Overview

readcon-core is structured as an hourglass API: a Rust core with
thin FFI and language-specific wrapper layers.

#+begin_example
  Python (PyO3)    Julia (ccall)    C++ (RAII header)
       \                |               /
        \               |              /
         +------- C FFI (ffi.rs) -----+
                        |
              Rust core library
         types | parser | writer | iterators
#+end_example

* Core types (types.rs)

- =FrameHeader= :: 9-line header metadata (cell, angles, atom counts,
  masses, spec_version, metadata). The =spec_version= field (=u32=)
  records the CON spec version from the JSON metadata line. The
  =metadata= field (=BTreeMap<String, serde_json::Value>=) holds
  additional key-value pairs from the JSON, preserving unrecognized
  keys through round-trips.
- =AtomDatum= :: Single atom data (symbol, coordinates, fixed flag,
  atom_id (original atom index before type-based reordering),
  optional velocities, optional forces).
- =ConFrame= :: Complete frame (header + atom data vector).
- =ConFrameBuilder= :: Builder pattern for constructing frames
  programmatically. Accepts an optional =metadata= map; the builder
  always sets =spec_version= to =CON_SPEC_VERSION=. Exposes typed
  setters for common keys like =energy=, =frame_index=, =time=, etc.

Symbol strings use =Arc<str>= to avoid per-atom string clones
within a type block and ensure thread-safety for parallel parsing.

* Parser (parser.rs)

- =parse_line_of_n<T>= :: Generic whitespace-separated value parser
  for header lines.
- =parse_line_of_n_f64= :: fast-float2 specialized parser for the
  coordinate/velocity hot path.
- =parse_line_of_range_f64= :: Flexible parser accepting between
  =min= and =max= columns, padding from defaults. Used for atom
  lines where column 5 (atom_id) is optional.
- =parse_frame_header= :: Consumes 9 header lines. Detects JSON
  metadata on line 2 (starts with ={=) for spec v2+; non-JSON line 2
  triggers legacy mode (=spec_version = 1=). Malformed JSON (starts
  with ={= but invalid) produces an error. Reserved metadata keys are
  checked against the spec schema.
- =parse_single_frame= :: Header + coordinate blocks. Accepts
  4-column atom lines (column 5 defaults to sequential index). In
  validate mode, coordinate labels, symbols, fixed flags, atom ids,
  cell geometry, atom counts, and masses are checked before the frame
  is accepted.
- =parse_velocity_section= / =parse_force_section= :: Optional
  velocity and force blocks after coordinates. Spec-v2 files declare
  sections in JSON metadata; files without a =sections= key can still
  auto-detect velocities by blank separator. Declared sections must be
  present at their declared position. If JSON metadata sets =validate=
  to =true=, section parser paths verify component symbols, exact
  labels, fixed masks, and atom ids against the coordinate blocks
  before attaching velocities or forces.

* Writer (writer.rs)

- =ConFrameWriter<W: Write>= :: Generic buffered writer.
- Line 2 is always serialized as a JSON object containing
  =con_spec_version= plus any entries from =frame.header.metadata=.
  The original =prebox_header[1]= value is overwritten. Managed keys
  (=con_spec_version= and =sections=) are regenerated by the writer;
  when =validate=true=, the writer emits an empty =sections= array for
  coordinate-only frames.
- Writes header, coordinate blocks, velocity blocks (if
  =frame.has_velocities()=), and force blocks (if
  =frame.has_forces()=).

* Iterators (iterators.rs)

- =ConFrameIterator= :: Lazy frame-by-frame parser with =next()= and
  =forward()= (skip without parsing atom data).
- =read_all_frames()= :: Convenience function using memmap2 for large
  trajectory files.
- =parse_frames_parallel()= :: Rayon-based parallel parsing behind
  the =parallel= feature gate.

* FFI layer (ffi.rs)

Opaque handle pattern:
- =RKRConFrame= :: Opaque Rust frame handle.
- =CFrame= / =CAtom= :: Transparent C structs for direct data access.
- Iterator lifecycle: =read_con_file_iterator= ->
  =con_frame_iterator_next= -> =rkr_frame_to_c_frame= ->
  =free_c_frame= -> =free_rkr_frame= -> =free_con_frame_iterator=.

Version and spec constants:
- =#define RKR_CON_SPEC_VERSION 2= :: Compile-time spec version for
  =#if= guards.
- =rkr_con_spec_version()= :: Runtime library spec version query.
- =rkr_library_version()= :: Returns library version string (e.g.
  ="0.5.2"=). The pointer is static; do not free it.

Per-frame metadata accessors:
- =rkr_frame_spec_version(handle)= :: Returns the spec version stored
  in a parsed frame's header (the version the file was written with,
  not the library's version).
- =rkr_frame_metadata_json(handle)= :: Returns the full JSON metadata
  line as a heap-allocated C string. The caller must free with
  =rkr_free_string()=.
- =rkr_frame_energy(handle)=, =rkr_frame_time(handle)=, etc. :: Typed
  accessors for common metadata keys.
- =RKRStatus= :: Prefixed C-compatible status codes such as
  =RKR_STATUS_SUCCESS=, =RKR_STATUS_NULL_POINTER=, and
  =RKR_STATUS_BUFFER_TOO_SMALL=.
- =rkr_status_message(status)= :: Returns a static human-readable
  message for every status value.
- Builder FFI functions preserve the full atom payload. Existing
  boolean fixed helpers remain available, while =*_fixed_mask=
  variants accept per-axis fixed flags. Force and velocity+force
  variants call the same Rust =ConFrameBuilder= paths as native Rust.

* C++ wrapper (readcon-core.hpp)

Header-only RAII wrappers with lazy caching:
- =ConFrameIterator= :: Range-based for loop support.
- =ConFrame= :: Cached accessors for cell, angles, atoms, headers,
  velocities, forces, and metadata helpers.
- =ConFrameWriter= :: RAII file writer.
- =ConFrameBuilder= :: Overloads for boolean fixed flags and
  =std::array<bool, 3>= masks, with velocity, force, and
  velocity+force atom constructors.

* Python wrapper (python.rs)

The PyO3 layer stores =ConFrame.atoms= as a live Python list and
=ConFrame.metadata= as a live Python dict. Writers validate metadata
as JSON-compatible values and convert the live atom list into a Rust
=ConFrame= at write time. =read_first_frame(path)= uses the Rust
first-frame reader; =iter_con(path)= exposes a Python iterator for
loop-oriented frame processing.

ASE conversion maps all-fixed atoms to =FixAtoms= and partial masks to
=FixCartesian=, preserving =atom_id= through a named ASE array.

* Julia wrapper (wrapper.jl)

The Julia ccall layer exposes typed frame metadata fields populated
from C FFI accessors. =write_con(path, frames; precision=6)=
reconstructs opaque frame handles through the C builder API and writes
them through the Rust writer, preserving velocities, forces, per-axis
fixed masks, masses, atom ids, and JSON metadata.
