IO Pipelines
- The crate exports
load_scene,load_trajectory, andload_frequency_data, each selecting the appropriate parser based on file extension hints and content sniffing. formats/contains format-specific parsers:xyz/,pdb/,cif/,gromacs/,vasp/,sdf.rshandle canonical structural formats.gaussian/andnwchem/include streaming parsers that expose run summaries (GaussianRunSummary,NwchemRunSummary), stage boundaries, and trajectory extraction helpers used by the viewer’s tasks panel.cube/,mrc/, andnbo/parse volumetric grids and natural bond orbital data for surface visualisation.
LoadOptionscontrols expensive or selective work. Triage callers usewithout_mo_coefficients(); multi-dataset volumetric callers usewith_volumetric_dataset(index).load_scene_with_options_and_progresscombines those choices with a byte callback.- Streaming helpers (
CountingReader,ProgressFn) support background loading with live byte progress and cancellation (backgroundin the UI uses these hooks). The callback receives(bytes_read, total_len_if_known)and returns whether parsing should continue. Large files are memory-mapped viaload_mapped(MappedText), and the summary parsers report progress from inside the parse loop through the sameProgressFnshape. Whole-text formats report the exact file length after mapping or copying rather than inventing intermediate percentages. registry.rsprovides theFormatHandlertrait and registry scaffolding; built-in handlers register throughformats/mod.rs::register_builtin_handlers().detection_registry.rs(pub mod detection_registry, exposingdetect_format) is a declarative table of extensions, content markers and priorities. It backs the TUI’s format label and the supported-extensions hint in error messages; what a file loads as is decided byFormatHandler::detect, not by this table.
Bounded source inspection
inspect_source_hints(path) reads at most 64 KiB of a supported source before canonical parsing or scene construction. It derives the first-frame atom count from an XYZ header and the atom count, grid dimensions, and grid-point count from a CUBE header. Gaussian FCHK reads its Number of atoms declaration. SDF reads the first record’s V2000 or V3000 atom and bond counts, while POSCAR/CONTCAR sums the species counts. PDB and CIF/mmCIF use bounded record scans. The scanners understand PDB MODEL records and logical mmCIF rows that wrap across lines or share one physical line. GRO reads the declared first-frame atom count and, for a complete source below the ceiling, counts concatenated frames without parsing their coordinate payload.
For application output, the inspector uses detection_registry.rs instead of maintaining another set of program banners. It reads declared atom counts from Gaussian, ORCA, Quantum ESPRESSO, DIRAC, and VASP vasprun.xml prefixes. NWChem counts rows only after its existing geometry parser recognizes a complete table. Molcas and Molpro can still return a detected format when the prefix contains no usable count. Unsupported formats return Ok(None) so the caller can retain its size-and-format estimate.
The returned SourceHints is serializable and includes source and inspected byte counts, the inspection ceiling, known canonical capabilities, optional scene counts, and SourceHintConfidence. A complete fixed header reports header_derived. A complete PDB or CIF/mmCIF source below the ceiling reports bounded_scan. A truncated structural record scan reports prefix_lower_bound; every count in that record is safe to read as “at least”, never as a total or an extrapolation. A declaration or complete table found in a truncated application output reports prefix_observed. That count describes work seen in the prefix, but a later stage may use a different system. format_detected means the program banner was found without a usable count. A supported fixed header that exceeds the byte ceiling reports extension_only and leaves its counts unset. A malformed complete header is an error.
This API is intentionally weaker than a parser. It does not inspect an XYZ atom table or a CUBE value block, and success does not establish that the payload is valid. Keep the later canonical parse and its ordinary validation. Do not extend this path by reading an unbounded line or materializing a canonical document; formats without fixed headers need a format-aware bounded scanner. Canonical export dry-run prints these hints, then performs its existing exact validation; the prefix result never substitutes for the canonical parse.
The inspection call is synchronous and reads only the source prefix. It does not start a format parser or background task, write the canonical cache, or mutate a viewer session. A frontend that cancels after showing the preflight has no parser or session work to unwind.
6.1 Adding a New Chemistry Format
Orbitron does not require a trait implementation to exist, but it does require one to load: load_scene, load_trajectory and the canonical pipeline all dispatch through the FormatHandler registry. The steps below are the ones ORCA needed, in the order it needed them; orca is deliberately the smallest complete example in the tree, and gaussian/nwchem show the same shape with streaming and multi-stage summaries on top.
Write the module under
io/pipelines/src/formats/<format>/, splitting parsing from the canonical builder (parsing.rs,canonical.rs,mod.rs). The crate deniesmissing_docs, and the workspace deniesunwrap/expectoutside tests, so everypubitem needs a doc comment and every fallible step needs?or an explicitelse.Implement
FormatHandler(io/pipelines/src/registry.rs) and register it fromformats/mod.rs::register_builtin_handlers():detectreturns aDetectionScorefrom the file’s contents. Several formats share.out, so match a marker the program prints for itself, not its name — the bare word appears in other programs’ prose and in file paths.parse_canonicalis the one that matters. Everything downstream reads the canonical document:inspect,.orbpackbundles, the desktop’s canonical load path, the web viewer. Its default implementation returnsUnsupportedFormat, so a handler with onlyparse_sceneloads in the viewer and exports an empty bundle, and nothing fails to say so.parse_scene/parse_trajectory/parse_frequencyare then usually one line each, taking whatparse_canonicalalready computed out of theCanonicalOutcome.
Add a detection-registry rule in
io/pipelines/src/detection_registry.rs. This table drives the TUI’s header label and the “supported extensions” hint in error messages — not loading. A format registered only here names itself correctly and loads nothing.Add fixtures and pin them. Real outputs under
io/pipelines/tests/corpus/<format>/, and a<path> text eol=lfline in.gitattributesin the same commit: a CRLF checkout shifts every line-structured parse, and this has caught four formats out already.Write tests,
io/pipelines/tests/<format>.rs. Beyond the obvious, two are worth having for every format: one asserting another program’s.outis not claimed by your detector, and one asserting the canonical document carries what you expect (see the trap in step 2).Add the fingerprint golden. Copy a neighbour’s
<format>_canonical_fingerprints_stablefunction inio/pipelines/tests/canonical_golden_formats.rsand generate the file withUPDATE_GOLDEN=1 conda run -n orbitron-dev cargo nextest run -p orbitron-io-pipelines --test canonical_golden_formats. These are hand-written per format rather than discovered, so a new format has no regression net until you add one.Document it in the user guide (
website/user-guide/data-support.qmd) and in §6.5’s capability matrix below.
Not required, though older versions of this page said otherwise: there is nothing to add to formats/loaders/. The per-format branches there exist to bypass the registry for formats that stream (Gaussian, NWChem) or that are not text (TREXIO); a registry format needs none of them. Nor do you need free parse_<format>_scene functions, or a streaming parser, until file size demands one.
Checklist: module ✅
FormatHandlerwithparse_canonical✅register()call ✅ detection rule ✅ fixtures +.gitattributes✅ tests ✅ fingerprint golden ✅ docs ✅
Changing an existing parser rather than adding one? Bump
CANONICAL_PARSER_REVISIONinio/pipelines/src/canonical/cache.rsin the same commit, or a file someone has already opened keeps returning the old document from the on-disk cache.
6.2 Canonical Pipeline & Attachments
Orbitron’s canonical pipeline decouples viewers and automation tooling from program-specific output formats. Every migrated parser produces a CanonicalOutcome that ultimately powers load_scene, load_trajectory, the CLI, and downstream bundles.
Key modules and types
io/pipelines/src/canonical/mod.rsdefines the canonical schema (CanonicalDocument), builder ergonomics (CanonicalBuilder), attachment metadata (AttachmentRef), and the enrichedCanonicalOutcomecontaining optional fast-path conversions plus the attachment payloads.formats::<format>::canonicalhouses the format-specific adapter that translates raw parse results into a canonical document and populates attachments.formats::load_canonicalreturns the fullCanonicalOutcome. It is preferred overload_canonical_documentwhen you need access to attachments or pre-computed scene/trajectory data.automation/cliexposesorbitron canonical export, which writes the manifest to<bundle>/manifest.jsonand each attachment to<bundle>/attachments/<sha>.<ext>.
Building a canonical parser
- Parse the format as usual (geometry snapshots, trajectories, frequencies, thermochemistry) and feed the results into
CanonicalBuilder. At a minimum, populatestructure; only advertise theTrajectory/Frequency/Thermochemistrycapabilities when the format truly supports them.- Use
canonical::base_builder_with_raw_source*helpers to seedSourceMetadata,Provenance, and araw_sourceattachment. Extend withbuild_mo_coefficients_attachment/build_volumetric_attachmentwhen emitting MO or grid payloads. The volumetric helper accepts aVolumetricAttachmentParamsstruct describing the grid geometry/metadata and returns both the attachment bytes and the dataset entry so callers can embed the resulting record in theirpayload.volumetric.datasets[]array. - For periodic/solid-state formats, prefer the shared helpers:
structure_section_from_partsaccepts atoms + unit cell + metadata closure so you can skip the boilerplate of creating a temporarySceneBuilder. This is now used by VASP and makes it straightforward to plug in future periodic parsers without duplicating schema wiring.build_periodic_electronic_structure(|periodic| { … })wraps thePeriodicElectronicStructurestruct and produces anElectronicStructureonly when periodic data exists. VASP now calls this helper so new periodic formats only implement the callback and reuse the shared schema wiring.
- Use
- Emit provenance via
ProvenanceInput(path + optional checksum). This keeps cache entries and downstream bundle fingerprints deterministic. - Create attachments for any material too large to remain inline. Typical attachments include:
- Raw source log/input (so users can reproduce the canonical document without the original file). Compute a SHA-256 digest, store the bytes, and commit
attachment.reference.metadataentries such asoriginal_extension,encoding, andsource_pathso exporters can restore the file name. - Molecular orbital coefficients (e.g., Pop=Full outputs). Trim the dense coefficient tables from the inline payload and store them as attachments (
gaussian:mo_coefficients,nwchem:mo_coefficients). Hydrate the coefficients by callingCanonicalOutcome::into_scene_graph()/.into_trajectory()before handing snapshots to callers. - Volumetric grids / trajectory shards: stream the binary data into a
Vec<u8>and populate shape/unit metadata. Keep one attachment per logical asset (e.g., HOMO cube, charge density); the standalone CUBE handler demonstrates the pattern by emitting avolumetric_gridattachment plus a manifest dataset entry keyed by the attachment id. - Pre-computed derived artefacts (e.g., rendered images, multi-format exports) can also be attached; annotate them clearly so consumers can differentiate raw vs derived data.
- Raw source log/input (so users can reproduce the canonical document without the original file). Compute a SHA-256 digest, store the bytes, and commit
- When emitting program-specific metadata, use
ProgramExtrasBuilderfromcanonical::helpers:ProgramExtrasBuilder::new("qe")(or"molpro","vasp", etc.) exposes fluentinsert(...)andwith_tasks(&summary.tasks)methods and returns anIndexMap<String, Value>ready forCanonicalBuilder::with_extras. This keeps every format’s extras under a single key and ensurestask_count/tasksare populated consistently.
- After collecting attachments, call
register_attachment_refs(builder, &attachments)followed byoutcome_with_attachments(document, attachments)when finishing the outcome. These helpers make it much harder to forget registeringraw_sourceentries or to leak bytes, and they centralise the logic we previously duplicated across every format. - PDB canonical structure: the PDB loader produces canonical documents with explicit
CONECTconnectivity, unit-cell metadata (when present), and secondary-structure hints encoded asSceneMetadatatags. Downstream consumers get these bonds automatically when callingload_canonical(...).into_scene_graph(). - Format-specific expectations
xyz/sdf/pdb/cif/gromacs-gro: Always emit araw_sourceattachment and usestructure_section_from_parts(or_from_snapshot_custom) so metadata wiring stays consistent. These lightweight formats now finish every builder by chainingregister_attachment_refs(...); outcome_with_attachments(...), so new text-based structures should mirror that sequence to avoid forgetting attachment references.gaussian/nwchem/molpro/molcas/dirac: Populate extras viaProgramExtrasBuilder("gaussian","nwchem", etc.), externalise MO coefficients into attachments where applicable, and callbuild_trajectory_positions_attachment/build_frequency_displacements_attachmentso the trajectory/frequency sections reference binary shards when present.qe: Usestructure_section_from_partsandbuild_periodic_electronic_structure. All QE metadata should live underextras["qe"](energies, relax profile, DOS/band/PDOS summaries) via the program extras builder. QE volumetric grids (.xsf) should be routed through the shared volumetric attachment helpers so overlays can reuse the CUBE pipeline.vasp: Reusestructure_section_from_partsto build the periodic structure, attach related artefacts (DOSCAR,PROCAR,EIGENVAL, charge grids), and rely onbuild_periodic_electronic_structurefor band/DOS payloads.- Volumetric bundles (
cubesingle-file handlers, volumetric directories) and standalone NBO summaries: run every attachment (raw sources, grids, population tables) throughregister_attachment_refs+outcome_with_attachmentsand keep provenance metadata in sync viabase_builder_with_raw_source*. Record dataset entries referencing the attachment id, shape, origin, voxel vectors, and source path.
- QE canonical structure and extras: the QE handler currently supports selected SCF and relax outputs (e.g.,
qm_tests/qe/benzene/scf.out,benzene/relax.out,graphene_scf.out,Si/scf.out,srtio3/srtio3.out), emitting canonical structures with periodic unit cells plus:extras["qe"].scf_total_energy_ry/scf_total_energy_hartree/fermi_level_ev,- a basic SCF task list (
extras["qe"].tasks), - inline band points parsed from QE’s
bands (ev):blocks for SCF runs, - lightweight DOS and band summaries for QE
*.datartefacts (dos_summary,bands_summary), - PDOS file summaries and packed grids for
*.pdos_*outputs (pdos_summary+ attachments), - volumetric grid attachments for cube-style
.xsfoutputs (payload.volumetric), - a relax energy profile (
relax_profile) when multiple SCF steps are present. These extras power CLIinspect/canonical exportflows today and are intended to feed a future QE panel in the viewer.
- Cross-program task summaries:
io/pipelines::tasksexposesProgramTaskSummaryplus helpers (nwchem_tasks_to_program_summaries,qe_tasks_to_program_summaries,collect_program_task_summaries). Canonical extras obtained fromload_canonical_documentnow drive bothorbitron inspectandorbitron infoso DIRAC/QE/NWChem tasks show up consistently in CLI output and JSON (program_tasksarray). Molpro extras also back the new--molpro-taskand--molpro-kind(alias--task) filters on both commands, letting contributors script against specific Molpro modules without re-tokenising the raw logs. Keep the extras JSON stable when adding task metadata so CLI filters remain robust.
- Store attachments in the
CanonicalOutcomeviawith_attachment/push_attachment. The document retains only the references; the data lives alongside the manifest and is retrieved throughoutcome.attachments().
Exporting canonical bundles
- Use
orbitron canonical export <source>to materialise the bundle. The command writes:manifest.json– the canonical document (pretty-print via--pretty).attachments/<sha>.<ext>– one file per attachment, whereextdefaults to the metadata’soriginal_extensionorbin.
- Consumers can reconstruct the
CanonicalOutcomeby reading the manifest and attachment files;orbitron canonical importalready verifies hashes and restores theraw_sourcepayload (with additional extract modes planned for volumetric grids and other artefacts).- Manage cached bundles with
orbitron canonical cache path|list|purge [digest](cache lives underORBITRON_CANONICAL_CACHEor the platform cache dir).
- Manage cached bundles with
- When adding new attachment types, update
automation/cli/tests/canonical.rs(or add a dedicated test) to assert the file name, byte content, and metadata. This keeps the bundle contract stable. Pair the CLI coverage with a canonical round-trip test (load_canonical(...).into_scene_graph()) so coefficient-style attachments rehydrate before downstream consumers inspect the scene.
Testing checklist
- Unit/integration tests should call
load_canonicalto ensure attachments are present and payloads are hash-stable between runs. - Extend format-specific tests (e.g.,
gaussian_log.rs,nwchem_out.rs,qe.rs) to assert thatdocument.source.formatand attachment/extra metadata match expectations. - Keep the smoke tests (
qm_smoke.rs) tolerant of canonical error wording (structure snapshot missing,no vibrational modes) so format migrations do not create brittle failures.
Import pipeline (orbitron canonical import)
orbitron canonical import <bundle>verifies attachment hashes and, when--outputis provided, restores theraw_sourcepayload to a user-specified location. Use--idto extract a specific attachment when multiple payloads exist. Additional extraction modes (e.g., writing volumetric grids) will iterate on this base command.- Cache hydration is wired up: each loader (
io/pipelines/src/formats/loaders/{scene,trajectory,frequency,canonical}.rs) callscache::load_cached_outcome_for_bytes(...)before parsing, so a file whose contents are already in the on-disk canonical cache is not re-parsed. The cache (io/pipelines/src/canonical/cache.rs) is keyed by file contents, and bundles carry a producer stamp ofCARGO_PKG_VERSION+CANONICAL_PARSER_REVISION. That stamp does not move between commits, so bumpCANONICAL_PARSER_REVISIONin the same commit as a change to canonical output, or a file you already opened keeps returning the old document. SetORBITRON_CANONICAL_CACHE=<tmpdir>when verifying parser work either way.
6.3 Task Metadata & Thermochemistry Pipelines
- Gaussian summaries record optimisation energy trajectories and frequency/intensity tables (
GaussianStageMeta::opt_energy_trajectoryand::frequency_modes). The TUI analysis panel reads these fields directly; keep the arrays small by truncating on display rather than during parsing. - NWChem summaries mirror the Gaussian contract with
NwchemTaskMeta::opt_energy_trajectoryand::frequency_modes. Frequency tasks are enriched withThermochemistryData, and Raman analyses are detected as their ownNwchemTaskKind::Raman. The canonical builder (and direct summary parser) look for.normalsidecars referenced by the log (Raman scattering data written to …) and store the parsed sticks/samples underNwchemTaskMeta::raman_spectrum. Downstream consumers (CLI, TUI, GUI) can turn that payload into exportable spectra viaorbitron_ui_shell::helpers::raman_spectrum_to_ir. Use the same pattern whenever a format exposes auxiliary files (e.g., DIRAC, GAMESS) so helpers stay reusable and UI panels can rely on typed metadata rather than ad-hoc JSON. - NWChem Task Outcome Detection: Each
NwchemTaskSummaryincludes aRunOutcomefield (Success,Incomplete,Failed, orUnknown) that tracks whether the task completed normally. The parser detects incomplete tasks by looking for expected termination markers (“Task times”) and checking for error conditions. Tasks have two key methods:is_complete()returnstrueonly ifoutcome == RunOutcome::Successhas_usable_data()returnstrueif the task contains expected data for its type (frames for optimizations, modes for frequencies, energies for single-points) regardless of completion statusselection_priority()returns a priority score (Optimization=3, Frequency=2, SinglePoint=1) used by auto-selection logic to prefer more informative task types The viewer’sauto_queue_latest_tasks()function (ui/shell/src/tasks.rs) automatically selects the best complete task when loading NWChem files usingfind_best_complete_nwchem_task(), which prefers tasks with higher priority scores and breaks ties by choosing later tasks. If no complete tasks exist, the function logs a status message and shows a toast notification without attempting to load data. The Analysis → Overview panel (ui/shell/src/panels/analysis/overview/tasks.rs) renders colored status circles next to each task: green (Success), yellow (Incomplete), red (Failed), gray (Unknown). Users can manually click incomplete tasks to load partial data—the viewer respectshas_usable_data()to determine if a task is clickable. When extending other format parsers to support per-task outcomes, follow theNwchemTaskSummarypattern: add anoutcomefield, implementis_complete()andhas_usable_data()helpers, update the auto-selection logic, and add status indicators to the relevant UI panel.
- When introducing new metadata, update
automation/tui/src/panels/analysis/(analysis view) and the GUI equivalent (ui/shell/src/panels/analysis/overview/tasks.rs) so both experiences stay in sync. Unit tests should cover the shape and ordering of the presented data to catch regressions early.
6.4 Parsing Utilities Reference
The io/pipelines crate provides shared parsing utilities in src/parsing_utils/. They are covered by focused unit tests in that directory and reused where formats share syntax. Parser-specific rules still belong in their format modules.
Key utilities:
parse_whitespace_tokens– Splits whitespace-separated values into aVec<&str>. It allocates the vector, but the tokens borrow from the input and do not allocate new strings.contains_any_marker– Multi-pattern string matching for format detection and section identification. Accepts a slice of marker strings and returnstrueif any are found.convert_bohr_to_angstrom– Unit conversion for atomic units. Handles both single values and coordinate triples with a consistent conversion factor (0.529177210903).parse_float_after_delimiter– Extracts floating-point values from “key = value” patterns. Handles scientific notation (including Fortran D-format) and returnsOption<f64>.parse_scientific_float– Robust float parsing with automatic Fortran D-format conversion (1.23D+05→1.23E+05). ReturnsOption<f64>instead of panicking.parse_coordinate_triple– Parses three consecutive floats from whitespace-separated tokens into[f64; 3]. Essential for geometry parsing across all molecular formats.
Additional utilities:
skip_empty_lines– Advances an iterator past blank linesparse_last_float– Extracts the last float from a lineparse_float_at_index– Gets a float from a specific token indexextract_element_symbol– Normalizes atomic symbols (e.g.,CA→Ca)
For current adoption per utility, grep -rl <name> io/pipelines/src is authoritative.
When to use parsing utilities:
Use
parse_whitespace_tokenswhen the parser needs indexed or repeated access to a token vector. Usesplit_whitespace()directly for a single iterator pass.Always use
parse_scientific_floatorparse_float_after_delimiterinstead of.parse::<f64>()when parsing floats from quantum chemistry logs – they handle Fortran D-format automatically.Always use
convert_bohr_to_angstromfor unit conversions instead of inline multiplication – it ensures consistent precision across the codebase.Use
parse_coordinate_triplewhen extracting XYZ coordinates from whitespace-separated tokens (common in XYZ, PDB, log files).Use
contains_any_markerfor format detection and section boundaries instead of chaining.contains()calls.
Example migration patterns:
// Before: Manual tokenization and parsing
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 4 {
let x = parts[1].parse::<f64>().ok()?;
let y = parts[2].parse::<f64>().ok()?;
let z = parts[3].parse::<f64>().ok()?;
// ...
}
// After: Using utilities
use crate::parsing_utils::*;
let tokens = parse_whitespace_tokens(line);
let coords = parse_coordinate_triple(tokens.get(1)?, tokens.get(2)?, tokens.get(3)?)?;// Before: Nested delimiter searches
if let Some(eq_pos) = line.find('=') {
if let Ok(value) = line[eq_pos + 1..].trim().parse::<f64>() {
energy = value;
}
}
// After: Single utility call
use crate::parsing_utils::parse_float_after_delimiter;
if let Some(value) = parse_float_after_delimiter(line, '=') {
energy = value;
}Adding a new format (updated workflow):
When implementing a new parser, follow §6.1 above, but also:
Import utilities at the top of your module:
use crate::parsing_utils::*;Use
parse_whitespace_tokenswhen the parser needs a token vectorUse
parse_float_after_delimiterfor “key = value” patternsUse
parse_coordinate_triplefor geometry parsingUse
convert_bohr_to_angstromfor atomic unit conversionsUse
contains_any_markerfor section detection
Quality standards:
- All utilities return
Option<T>orResult<T>(no panics) - Fortran D-format scientific notation is handled automatically
- Token helpers borrow
&strslices where practical; collection helpers may still allocate their container - Focused unit tests cover finite floats, Fortran exponents, tokenization, k-point paths, units, and marker detection
- Generic over
AsRef<str>for caller flexibility
When to create a new utility:
Consider adding a new utility to parsing_utils/ when: - The pattern appears 3+ times across different format parsers - The logic is non-trivial (e.g., requires format conversion or bounds checking) - Edge case handling is important (scientific notation, whitespace variations) - Consistency is critical (unit conversions, coordinate parsing)
For detailed documentation, see the module-level rustdoc on these utilities in io/pipelines.
6.5 Format Capabilities Reference
Orbitron supports 19 format families spanning structural data, quantum chemistry calculations, wavefunction containers, periodic systems, and volumetric grids. This section describes each family’s capabilities, limitations, and implementation path.
Format Capabilities Matrix
| Format | Extensions | Structure | Trajectory | Frequency | Orbitals | Periodic | Thermo | Streaming |
|---|---|---|---|---|---|---|---|---|
| XYZ | .xyz |
✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ |
| GROMACS GRO | .gro |
✓ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ |
| GROMACS XTC | .xtc |
paired service | lazy | ✗ | ✗ | ✓ from frames | ✗ | indexed lazy access |
| PDB | .pdb, .ent, .brk |
✓ | ✗ | ✗ | ✗ | ✓ | ✗ | ✓ |
| CIF (incl. mmCIF) | .cif |
✓ | ✗ | ✗ | ✗ | ✓ | ✗ | ✓ |
| SDF | .sdf, .mol |
✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| VASP | POSCAR, CONTCAR, vasprun.xml, XDATCAR |
✓ | ✓ | ✗ | ✗ | ✓ | ✗ | ✓ |
| Gaussian | .log, .out, .gjf, .fchk, .cube |
✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ |
| ORCA | .out |
✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✗ |
| NWChem | .out, .nw |
✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ |
| CUBE | .cube, .cub, .xsf |
✓ | ✗ | ✗ | ✓ | ✗ | ✗ | ✓ |
| MRC2014 / CCP4 | .mrc, .map, .ccp4 |
cell only | ✗ | ✗ | density | ✓ when crystallographic | ✗ | memory-mapped source |
| NBO | .nbo, FILE47 |
✓ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
| MOLDEN | .molden |
✓ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
| TREXIO | .h5, .trexio.h5 |
✓ | ✗ | ✗ | ✓ | optional cell | ✗ | HDF5 datasets |
| DIRAC | .out, .h5 |
✓ | ✗ | ✗ | ✓ | ✗ | ✗ | ✓ |
| Molpro | (output), .xml |
✓ | ✗ | ✓ | ✗ | ✗ | ✓ | ✓ |
| Molcas | (output) | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✓ |
| Quantum ESPRESSO | .out, .in, .xml, .xsf, .dat, .dos, .pdos_*, .bands, .bands.gnu, .UPF |
✓ | ✗ | ✓ | ✗ | ✓ | ✗ | ✓ |
XYZ (.xyz)
Capabilities: - Multi-frame trajectory support (native format) - Extended XYZ with per-atom charges, velocities, and forces (parsed from comment line) - Comment line metadata (labels, step numbers, energies) - Automatic element detection from symbols or atomic numbers - Bond inference via SceneBuilder::infer_covalent_bonds
Implementation: io/pipelines/src/formats/xyz/mod.rs - Parser: Line-by-line streaming with parse_whitespace_tokens - Detection: “atom count” header + element symbols + Cartesian coordinates - Canonical: Structure section + optional trajectory positions attachment - Streaming: ✓ (frame-by-frame for large trajectories)
Limitations: - No explicit bond information (all bonds inferred) - No periodic boundary conditions - Element types must be consistent across all frames - Comment line metadata format is not standardized
Test coverage: 25 fixtures (io/pipelines/tests/fixtures/xyz/)
GROMACS GRO (.gro)
Capabilities: - Fixed-width residue number/name and atom name/number fields. - Variable coordinate precision, with nm converted to Å at the parser boundary. - Optional velocity vectors in nm/ps. - Three-value orthorhombic and nine-value triclinic box records. - Concatenated frames with title time metadata. - Single-frame export with structured loss warnings.
Implementation: io/pipelines/src/formats/gromacs/ - gro.rs parses every frame, validates stable atom identity, builds the last frame as the canonical structure, and records all frames as an MD trajectory. - Source five-column numbers remain in gro:residue_number and gro:atom_number. Logical residue numbers are unwrapped in 100,000-residue increments so rollover does not merge unrelated residues. - Optional velocities are stored on the structure atoms and in canonical trajectory extras. Positions also use the shared packed trajectory attachment. - core/services/src/exporter/gro.rs owns fixed-width writing and returns a GroExportReport; frontends must display every warning.
Limitations: - GRO has no force-field topology, bonds, formal charges, isotope masses, or molecular charge/multiplicity fields. - The writer emits one frame. Concatenated GRO writing is deferred until a real workflow requires it. - An unsupported cell orientation is refused instead of rotating the system silently. - XTC is implemented separately at the services boundary because it needs a companion topology and path-based random access. TPR, TRR, TOP, and ITP remain separate future formats.
Differential gate:
conda run -n orbitron-dev python scripts/gromacs_gro_differential.py \
io/pipelines/tests/fixtures/gromacs \
--binary target/release/orbitron \
--report notes/gromacs-gro-differential-report.jsonThe GROMACS 2026.3 container checks each source and Orbitron write. The script compares atom/frame counts, every position, full cells, source times, and last-frame velocity presence. Its JSON output keeps agreements, disagreements, Orbitron refusals, and reference refusals separate.
GROMACS XTC (.xtc)
core/services/src/xtc_source.rs owns read-only, indexed XTC access. It uses the exactly pinned molly 0.6.1 codec and calls only read_frame_at_offset::<false>, which avoids the codec’s optional unsafe buffered path. Every codec call is inside catch_unwind because the upstream header reader asserts that XTC’s repeated atom counts agree. A malformed file therefore becomes a normal load error rather than aborting the desktop.
IndexedXtcSource stores a topology snapshot, frame offsets and summaries, and a three-frame LRU cache. It reopens the file for each requested frame, converts nm to Å at decode, and replaces only positions, cell, and frame provenance. Atom identity, residues, bonds, and other topology fields remain shared.
The topology contract has two evidence levels. Matching atom count and first-frame coordinates within XTC precision yields coordinate_verified. Count-only pairing requires an explicit caller policy and yields user_confirmed_atom_order; frontends must show its warning. Count mismatch always fails.
The persistent differential gate compares Orbitron with GROMACS 2026.3 and Chemfiles 0.10.4:
python scripts/gromacs_xtc_differential.py \
--orbitron target/release/orbitron \
--trajectory io/pipelines/tests/fixtures/gromacs/xtc-reference.xtc \
--topology io/pipelines/tests/fixtures/gromacs/xtc-topology.gro \
--report notes/gromacs-xtc-differential-report.jsonPDB (.pdb, .ent, .brk, .pdb1)
Capabilities: - Biological macromolecule structures (proteins, DNA/RNA, ligands) - CRYST1 unit cell parameters for crystallographic data - CONECT records for explicit bond connectivity - Secondary structure annotations (SSBOND, HELIX, SHEET) - Residue, chain, and atom numbering - Alternate-location identifiers, B-factors (temperature factors), and occupancy values - Declared polymer sequences from SEQRES, joined to coordinate residues and exact REMARK 465 missing-residue identities under the shared pdb:polymer_sequence metadata tag - Multi-model files (NMR ensembles) exposed as an ordered model collection. Source MODEL numbers survive as pdb:model_number; reordered rows are aligned by atom serial. The collection uses trajectory:kind=ensemble and leaves step absent because model order is not elapsed time.
Implementation: io/pipelines/src/formats/pdb/mod.rs - Parser: Record-based with fixed column positions - Detection: “ATOM”/“HETATM” records + PDB column layout - Canonical: Structure section with residue/chain metadata, CONECT bonds - Streaming: ✗. parse_scene_stream accepts a reader but currently buffers it before parsing.
Limitations: - Models with different atom identities cannot share one structure. Orbitron keeps the first model and reports how many were skipped. - No quantum mechanical properties (energies, orbitals, frequencies) - Bond connectivity is optional (CONECT records not always present) - Fixed-width format can be fragile with malformed files
Test coverage: 7 fixtures including proteins, DNA, metal clusters
CIF (.cif, including mmCIF)
Capabilities: - Crystallographic Information File (IUCr standard). - Unit cell parameters (a, b, c, α, β, γ). - Fractional and Cartesian atomic coordinates. - Space group symmetry operations (stored under crystal:symops for the Edit→Cell→Apply symmetry workflow). - Atomic occupancy and anisotropic displacement parameters (U_11..U_23). - mmCIF support for macromolecular structures: chain / residue / insertion-code metadata on _atom_site rows. Author identifiers remain the displayed residue identity, while _atom_site.label_asym_id and _atom_site.label_seq_id are retained as pdb:label_asym_id and pdb:label_seq_id for exact cross-reference. Biological assemblies via _pdbx_struct_assembly_gen + _pdbx_struct _oper_list; secondary-structure ribbons via _struct_conf (helix) and _struct_sheet_range (sheet) loops, written to the same pdb:helix_ranges / pdb:sheet_strands metadata tags the PDB parser produces so the existing ribbon renderer (core/ribbon/src/pass1/ ss.rs) lights up unchanged. - _struct_conn source annotations in pdb:struct_conn. Covalent, disulfide, modified-residue, and metal-coordination rows become fragmentary explicit bonds; hydrogen bonds and salt bridges remain annotations. Partner lookup accepts both label and author identifier namespaces. - Every _atom_site alternate location is retained in pdb:alt_loc. A named _struct_conn partner resolves to that exact location. - _pdbx_poly_seq_scheme supplies the declared polymer order and both author and label identities. _pdbx_unobs_or_zero_occ_residues marks the exact entries without coordinates. Both become pdb:polymer_sequence metadata for the shared residue model. - Loop and single-row _chem_comp categories retain component type, full name, formula, formula mass, and standard parent under pdb:chemical_components. _atom_site.label_comp_id is retained when it differs from the author-facing residue name. The shared residue model uses the controlled component type to classify modified peptide, DNA, and RNA monomers, while peptide-like and non-polymer remain non-polymers. - Bond inference from unit cell + atomic radii, merged with supported _struct_conn bonds.
Implementation: io/pipelines/src/formats/cif/mod.rs - Parser: CIF data-block parser with loop/value extraction (parse/mod.rs), per-section sub-modules (atoms.rs, cell.rs, assembly.rs, components.rs, secondary.rs, helpers.rs). - Detection: data_ blocks + _cell_length_a / _atom_site tags. - Canonical: Periodic structure section with unit cell metadata, fragmentary source bonds, pdb:* tags for biological assemblies, ribbons, and structure connections, plus an ensemble trajectory for multi-model mmCIF files. - Streaming: ✗. The reader buffers the complete file because cell, atom, and connection categories may appear in any order.
Model ensembles: _atom_site.pdbx_PDB_model_num becomes an ordered model collection tagged trajectory:kind=ensemble. Source model numbers are retained; frames have names such as Model 10 and no time or step value. Rows may be reordered between models because alignment uses label and author atom identity. Missing, duplicate, or changed identities make the ensemble unsafe to combine, so the first model remains available and cif:skipped_models:<count> reports what was dropped. Symmetry-generated sites are recomputed from each model’s fractional coordinates.
The wwPDB placeholder for a non-crystallographic structure, a 1 Å cubic P1 cell with right angles and Z=1, is source metadata rather than a physical periodic cell. Both PDB and mmCIF readers recognize the complete tuple and leave the scene nonperiodic. This matters for NMR and electron-microscopy entries: treating the placeholder as real would make PBC measurements and neighbor searches use a 1 Å box. See the wwPDB CRYST1 specification.
Limitations: - Only identity-symmetry _struct_conn rows currently resolve. Rows that use another symmetry operator warn and remain in source metadata. - Semicolon-delimited multiline _chem_comp display fields are not retained; ordinary quoted and bare values are. - Complex loops may skip unrecognized tags silently.
Asymmetric-unit expansion: a CIF stores only the symmetry-distinct sites, so expand_asymmetric_unit (io/pipelines/src/formats/cif/parse/mod.rs) runs unconditionally on load and records a cif:expanded_asymmetric_unit:<before>:<after> warning. Without it, a perovskite loaded with one oxygen instead of three, and formula, bond perception, coordination, and point-group detection were all computed on a structure that does not exist.
A file that names a space group but supplies no operations is resolved against the space-group table in orbitron-symmetry (core/symmetry/src/spacegroup/, generated from spglib by core/symmetry/scripts/generate_spacegroup_table.py) and tagged cif:space_group_from_table:<number>:<hall symbol>. Resolution needs a setting, not just a symbol: the 230 groups have 530 settings between them. A Hall symbol names one outright, as does an H-M symbol with an explicit :2 / :H suffix; a bare symbol means the setting the International Tables list first. Hexagonal versus rhombohedral axes come from the cell metric.
Origin choice 1 versus 2 (24 groups, including Fd-3m) is the one split no symbol can settle, so the listed coordinates decide it: a candidate whose expansion puts atoms closer than 0.7 Å is discarded (nothing in a crystal is shorter than H₂ at 0.741 Å), and among what survives the smallest expansion wins, because a CIF lists each site once and on the special position of the setting its author used. Spinel separates on the first rule (the wrong origin collapses Mg onto its own image at 0.28 Å) and diamond on the second (8 atoms against 16). Both rules tying leaves the file unexpanded with cif:ambiguous_space_group_setting:<number>:<choices>, which is what the diamond and silicon fixtures hit: each lists both origins’ 8a site, so the evidence is symmetric under the choice being made.
An inferred setting is tagged cif:space_group_setting_from_sites:<number>:<choice>:<hall> rather than cif:space_group_from_table:…, because reporting what a file says and reporting a conclusion drawn from it are different claims. 199 of the 230 groups resolve from a bare number, 206 once a cell is present, and the remaining 24 whenever the coordinates are decisive.
The operations are still kept in the crystal:symops metadata tag, so Edit → Cell → Apply symmetry can re-apply them by hand.
Test coverage: 8 crystallography fixtures (diamond, NaCl, silicon, perovskites, spinel, benzene) plus the parses_mmcif_secondary_structure_into_ribbon_tags integration test for biology mmCIF helix / sheet loops.
SDF/MOL (.sdf, .mol)
Capabilities: - MDL Molfile V2000 and V3000 formats - Explicit bond connectivity with bond orders (single/double/triple/aromatic) - Connection table with 3D coordinates - Property data blocks (SD file format) - Formal charges and radical flags
Implementation: io/pipelines/src/formats/sdf.rs - Parser: Fixed-format counts line + atom/bond blocks. A counts line containing V3000 dispatches to parse_v3000, which reads the CTAB’s own counts — a V2000 reader would load such a file as an empty scene, because V3000 zeroes the V2000 counts line. - Detection: “V2000” tag + connection table structure - Canonical: Structure section with explicit bonds + property metadata - Streaming: ✗ (full file read required)
Limitations: - No 3D property fields (QM energies, charges) - Formal charge encoding is limited to ±3 - Large SD files (thousands of molecules) load slowly without streaming
Test coverage: 7 fixtures (aspirin, benzene, ethanol, 2-butanol, trimethylammonium, combined samples, and caffeine_v3000.sdf for the V3000 path)
VASP (POSCAR, CONTCAR, vasprun.xml)
Capabilities: - Periodic solid-state structures with lattice vectors - Direct (fractional) and Cartesian coordinate modes; first-letter VASP shorthand (c/C/k/K = Cartesian, d/D/f/F = Direct, s/S = Selective dynamics) honoured - VASP-4.x POSCARs (no species line) fall back to a sibling POTCAR for element identity, with PAW pseudopotential suffix stripping (Si_d_GW → Si) - Inline-comment scale lines (0.52918 ! scaling parameter) - vasprun.xml: Total density of states (DOS), band structure, Fermi energy, per-atom forces (final ionic step) via parse_vasprun_final_forces - DOS/band structure export to CSV + PNG plots - Volumetric: CHGCAR / CHG / PARCHG parsed by parse_chgcar (in chgcar.rs); spin-polarized runs surface as two VolumetricData blocks (total + spin density). Routed through volumetric_loader so the same Surfaces pipeline cube files use serves CHGCAR. - OUTCAR parser (outcar.rs::parse_outcar) extracts last-step TOTAL-FORCE (eV/Angst) block + magnetization (x) per-atom moments. Drives D4 force arrows + D5 magmom halo. - XDATCAR parser (xdatcar.rs::parse_xdatcar) returns multi-frame Trajectory (constant-cell only — variable-cell NPT uses first-frame lattice). - ACF.dat Bader output (bader.rs::parse_acf_dat) returns per-atom electron populations; net charge Z − population feeds the Bader halo (AtomColorScheme::VaspBaderCharges). - Sources sub-tab (sources_runtime.rs + viewer/core/src/ui_state/sources/vasp.rs) auto-detects all VASP filenames and surfaces the run’s directory contents with Loaded/Detected/Missing badges. POSCAR ↔︎ CONTCAR rows expose a Compare button that pushes the sibling as a scene overlay for relaxation diffs. - Cell-conversion edit commands: LinearCellTransformCommand (primitive ↔︎ conventional for cF/cI/oF/oI/tI/hR), CleaveSlabHklCommand ((hkl) slab cut + vacuum padding), and the existing supercell / Niggli / wrap operations.
Implementation: io/pipelines/src/formats/vasp/mod.rs - Parsers: poscar.rs, parse.rs (vasprun.xml, roxmltree DOM), chgcar.rs, outcar.rs, xdatcar.rs, bader.rs, doscar.rs, procar/, kpoints.rs - Detection: POSCAR (lattice vectors + scaling + element list), XML (“vasprun” root), VASP filename match for extensionless files - Canonical: periodic structure + electronic structure. canonical.rs also merges three sibling files into the band data, each behind a shape check so a leftover file from another run in the same directory cannot be joined in silently: - PROCAR → BandStructure::projections (must agree on spins × k-points × bands) - DOSCAR per-ion blocks → PeriodicElectronicStructure::projected_dos (must agree on atom count) - KPOINTS → kpoint_labels, taken from the trailing ! X comments - <generation param="…"> classifies the run as KpointSampling::Path (listgenerated) or Mesh, which is the only thing in vasprun.xml that distinguishes a band path from an SCF grid - Band gap is computed at load time via BandStructure::compute_band_gap - Streaming: none. A second quick-xml reader existed behind a vasp_quick_xml feature that nothing enabled; it was deleted in 4a858d10 - Auto-collection of companion files (DOSCAR / PROCAR / OUTCAR / ACF.dat / vasprun fallback for forces) lives in ui/shell/src/viewer_loop/background_events/vasp.rs::hydrate_vasp_bundle_artifacts and runs whenever the active scene’s parent directory holds matching files.
Removed in 2026-05: the .zip/.tar.gz/.tgz archive-import flow (session/bundles.rs::extract_zip_bundle / extract_tar_bundle / select_bundle_entry, plus BundleUiState::bundle_mounts / current_bundle_root / last_vasp_bundle_scan). VASP runs now open by selecting any canonical file directly; companions resolve via Sources. zip / tar / flate2 were dropped as ui-shell dependencies.
Limitations: - POSCAR/CONTCAR: single structure only (XDATCAR covers trajectories instead) - Electronic structure requires vasprun.xml (not available from POSCAR alone) - Projected DOS needs LORBIT >= 10; a run without it has no per-ion blocks to read - High-symmetry labels need a line-mode KPOINTS beside the run. They are not derived from the lattice, so a run without one gets unlabelled corners rather than guessed names - Large XML files (1+ GB) require significant memory for DOM parsing - Per-atom magmom from vasprun.xml requires integrating spin-decomposed partial DOS (not implemented; OUTCAR is the canonical source) - XDATCAR variable-cell NPT runs use first-frame lattice for all frames
Test coverage: unit tests in each parser file + integration tests in io/pipelines/tests/vasp.rs (corpus-gated qm_tests_corpus_sanity walks every directory under the in-repo, gitignored io/pipelines/tests/corpus/vasp/, resolved via CARGO_MANIFEST_DIR, and asserts every POSCAR loads cleanly).
Gaussian (.log, .out, .gjf, .com, .cube, .fchk)
Capabilities: - Multi-stage jobs (Link1) with stage boundaries - Optimization trajectories (geometry steps + energies) - Vibrational frequencies with IR/Raman intensities - Molecular orbitals (CUBE files, formatted checkpoint data) - Population analysis (Mulliken, Löwdin, Natural/NBO if available) - Electronic excited states (TD-DFT, CIS) - Method and basis set extraction - SCF convergence details - Thermochemistry data (zero-point energy, enthalpy, free energy)
Implementation: io/pipelines/src/formats/gaussian/mod.rs - Parser: Line-by-line with section markers (“Standard orientation”, “Frequencies”, etc.) - Detection: “Gaussian” header + “Copyright” line - Canonical: Multi-stage summary with attachments for MO coefficients, trajectory positions - Streaming: ✓ (stage boundary loaders: gaussian_stage_scene_by_boundary)
Limitations: - Complex multi-stage parsing requires robust stage detection - FCHK reads geometry, Mulliken charges, permanent dipole, alpha/beta orbital energies and coefficients, electron counts, and Gaussian basis data. It does not replace the log parser for task history, frequencies, or thermochemistry. - Checkpoint files (.chk) require formchk preprocessing - Some population methods (Hirshfeld, CHELPG) not fully extracted
Test coverage: 30+ fixtures including optimization, frequency, TDDFT, NBO jobs
NWChem (.out, .nw, .movecs, .hess)
Capabilities: - Multi-task detection (Optimization, Frequency, Raman, Single-Point, Property) - Task-level outcome tracking (Success, Incomplete, Failed, Unknown) - Optimization trajectories with per-step energies - Vibrational frequencies + IR intensities - Raman spectrum parsing from .normal sidecar files - Molecular orbital coefficients (truncated from .out’s top-N table; full nbf × nmo from .movecs) - Basis-set definition parsed from the Basis "ao basis" printout into a GaussianBasisSet (Cartesian d/f only — pure-spherical d not yet permuted) - Population analysis (Mulliken, Löwdin, Natural) - TDDFT excited states - Thermochemistry data - Task byte boundaries for efficient random-access loading - Cartesian Hessian (.hess) parser + Jacobi mode synthesis (mass-weighted projection, 5-6 rigid-body modes removed) — turns a stand-alone .hess into FrequencyData even without an .out freq block - Input deck (.nw) structured summary: charge, nopen, basis libraries, ECP, xc, task list
Implementation: io/pipelines/src/formats/nwchem/mod.rs - Parser: Modular task scanner + per-task metadata extraction - Detection: “Northwest Computational Chemistry Package” or “NWChem” header - Canonical: Task summaries with outcome + attachments (MO coefficients, trajectory shards, Raman spectra) - Streaming: ✓ (task boundary loaders: nwchem_task_scene_by_boundary, nwchem_task_trajectory_by_boundary) - Basis attachment: parse_basis_set + build_gaussian_basis in basis_set.rs. The trajectory loader parses the basis once for the whole task and stamps it onto every frame’s electronic_structure, including frame 0 (which is what the viewer displays as the active scene). - Movecs: parse_movecs in movecs.rs handles Fortran unformatted records with auto-detected i32/i64 integer width. - Hessian: parse_hessian + frequencies_from_hessian in hessian.rs; uses the pure-Rust Jacobi solver in core/math/src/jacobi.rs.
Sources Load handlers (ui/shell/src/viewer_loop/runtime/redraw/panels/sources.rs and companions.rs): - RoleId::NwchemHessian → handle_nwchem_hessian_load: synthesises FrequencyData and switches to Vibrations. - RoleId::NwchemMovecs → handle_nwchem_movecs_load: attaches MOs as MolecularOrbital records (preserving atom-prefixed labels harvested from prior .out-parsed MOs so the halo overlay still works), populates GaussianBasisSet.mo_coefficients_alpha/beta when the basis is present (with a Cartesian d-shell column permutation xx,xy,xz,yy,yz,zz → FCHK xx,yy,zz,xy,xz,yz), and switches to Orbitals. - RoleId::NwchemInput → toasts a deck summary; only swaps the active scene when none is loaded. - RoleId::NwchemCivecs → parses CI/TDDFT amplitudes, computes particle/hole Natural Transition Orbitals against the loaded SCF MOs, attaches them to the active scene, and makes them available to Orbitals and Surfaces.
Limitations: - Complex output format with many task types (some partially supported) - TDDFT features limited to basic excitation energies - Periodic DFT (plane-wave) outputs not fully parsed - Some advanced property analyses (response, NMR) have limited extraction - Pure-spherical d/f from NWChem (uncommon — 6-31G*, def2 default to Cartesian) needs a separate column permutation table; today it falls back to fchk_conversion’s 5D→6D mapping which expects Gaussian’s pure-d ordering, not NWChem’s - .zmat is detected but has no dedicated parser. .civecs is parsed for excited-state amplitudes and NTO construction; it still requires the parent scene’s SCF orbitals from .out plus .movecs.
Test coverage: Test corpus with energy, optimization, frequency, Raman jobs; .movecs corpus walks io/pipelines/tests/corpus/nwchem/ cleanly across 21 binary files; .hess parses CO2 and ammonium fixtures; basis-set attachment verified end-to-end on ammonium (NH4+ in 6-31G* → 23 nbf, 14 shells over 1 N + 4 H).
Key Feature: Per-task outcome detection (NwchemTaskSummary::outcome) allows smart auto-selection and UI status indicators (see §6.3).
CUBE (.cube, .cub, .xsf)
Capabilities: - Volumetric grid data (molecular orbitals, electron density, electrostatic potential) - 3D regular grid with origin, voxel vectors, and point data - Atomic coordinates embedded in header - Directory mode: multiple CUBE files with shared geometry validation - Lazy grid loading for memory efficiency - Marching cubes isosurface generation - Program-agnostic format (Gaussian, NWChem, QE, ORCA, etc.)
Implementation: io/pipelines/src/formats/cube/mod.rs - Parser: Header (atom count, origin, axes, atoms) + grid data block - Detection: Atom count + origin + three axis vectors - Canonical: Volumetric attachment + dataset metadata - Streaming: ✓ (header parsed first, grid streamed separately)
Limitations: - Multi-dataset and multi-orbital CUBE files are supported and deinterleaved; a normal scene load chooses the first dataset. Use canonical export --mo N to package one requested dataset without retaining every grid. - Large grids (500³ points) require significant memory even with lazy loading - No standard naming convention (HOMO.cube vs homo_001.cube varies by program) - .xsf format treated identically to CUBE (QE-specific features ignored)
Test coverage: Multiple CUBE files for orbitals, density, potential
MRC2014 / CCP4 (.mrc, .map, .ccp4)
Capabilities:
- Scalar voxel modes decoded with byte-order handling from the
mrccrate - Stored column/row/section order permuted into Orbitron’s Cartesian X/Y/Z grid
ORIGINplacement, withNXSTART/NYSTART/NZSTARTas the fallback- Unit-cell sampling and non-orthogonal cell vectors
- Header minimum, maximum, mean, RMS, labels, space-group code, and format version retained in canonical metadata
- Experimental-density contour seeded from the declared mean and RMS
- Selection-local Cartesian clipping before the surface memory gate, decimation, and marching cubes, with the captured bounds retained in sessions and presentation state
- Raw-source and packed-grid attachments with full-file SHA-256 provenance
Implementation: io/pipelines/src/formats/mrc/
The parser produces the same CubeFile and VolumetricData types used by CUBE, XSF, and CHGCAR. This keeps desktop surface generation, session capture, CLI rendering, and Web View replay on one path. The 1 KiB source-hint reader is separate from voxel decoding and reports exact stored grid dimensions to the resource gate.
ISPG = 0 retains the cell basis but marks the map nonperiodic. Codes 1 through 230 retain a periodic crystallographic cell. Volume stacks (ISPG 401 through 630), complex Fourier modes 3 and 4, and compressed files are rejected.
The source preflight reads only the fixed header. A cold parse of the public 512³ EMD-38398 map peaked at 1,091,452,928 bytes, 1.65 percent above the 1,073,743,872-byte count-aware projection. Selection clipping lowers retained mesh work; it does not lower source parsing or normalized-grid retention.
Test coverage: little- and big-endian headers, nonstandard axis order, nonzero origin and start-derived placement, skewed cells, nonperiodic reconstructions, truncation, full-file provenance, independent mrcfile comparison, and the paired EMD-3001/4ZNN registration fixture.
NBO (.nbo, FILE47)
Capabilities: - NBO7 archive (FILE47) parsing for natural population analysis - AO basis function metadata - Natural orbital coefficients (from .37 plot files) - Orbital labels and occupancies (from .46 files) - Integration with Gaussian/NWChem for combined QM+NBO analysis - Population tables (Natural charges, Wiberg bond indices)
Implementation: io/pipelines/src/formats/nbo/mod.rs - Parser: FILE47 binary parser + associated text files - Detection: “NBO” or “FILE47” markers in .nbo or .47 files - Canonical: NBO summary with population extras + optional basis/geometry from FILE47 - Streaming: ✗ (requires full FILE47 read)
Limitations: - Requires NBO7 output format (NBO6 and earlier not supported) - Limited to NBO-specific data (no general QM properties) - FILE47 sidecar must be present for basis/geometry reconstruction - Orbital visualization requires separate CUBE generation
Test coverage: NBO fixtures with and without FILE47 sidecars
DIRAC (.out, .h5)
Capabilities: - Relativistic quantum chemistry calculations (1-, 2-, and 4-component spinor formalism). - TDDFT excited states with spin-orbit coupling. - Gross population analysis (Mulliken). - Symmetry-resolved orbitals. - Task detection (SCF, DFT, RESOLVE, TDDFT). - HDF5 checkpoint (.h5) reader: MO coefficients, eigenvalues, occupations, AO basis, molecule geometry. Quaternion-units nz (1 / 2 / 4) is honoured — for nz ≥ 2 the dominant-z slice is selected per MO so β-spinor-dominant orbitals (which leave z=0 near zero) are recovered correctly. - Large-component AO basis reconstructed into a GaussianBasisSet so the Surfaces tab can render relativistic MOs as isosurfaces.
Implementation: - io/pipelines/src/formats/dirac/mod.rs — task boundary scanner and metadata extraction for .out text outputs. - dirac/checkpoint/ — HDF5 checkpoint reader split into types.rs (public DiracCheckpointData), coefficients.rs (orbital-coefficient slicing with nz handling), aobasis.rs (Large-component basis → GaussianBasisSet), datasets.rs (HDF5 walk helpers), and labels.rs (basis-function labels). - Detection: “DIRAC” / “Dirac” header lines for .out; HDF5 signature byte test for .h5. - Canonical: Task summaries with relativistic metadata. - Streaming: ✓ for .out; .h5 loaded fully (small).
Limitations: - Specialized for relativistic methods (not general-purpose QM). - Geometry optimization tracking is limited. - Some advanced features (KRCI, Fock-space CC) have minimal support. - Phase information from quaternion components z=1..nz-1 is discarded when extracting the dominant real-major slice. - Small-component (/input/aobasis/2) basis is not used for isosurface rendering — chemistry-relevant orbital structure lives in Large.
Test coverage: DIRAC output fixtures for SCF, TDDFT, population tasks. .h5 real-fixture tests use the user’s qm_tests/dirac/ paths (gracefully skip on CI).
Molpro (output files, .xml)
Capabilities: - Multi-reference methods (CASSCF, MRCI, CASPT2) - Correlated calculations (CCSD, CCSD(T), MP2) - Frequency analysis - Task detection with program/method identification (RHF/UHF, CCSD, MULTI, OPTG, FREQ) - XML sidecar parsing for extended metadata - Thermochemistry data
Implementation: io/pipelines/src/formats/molpro/mod.rs - Parser: Task scanner + XML manifest reader - Detection: “Molpro” + version line, or XML root element - Canonical: Task summaries with correlated energies + method metadata - Streaming: ✓ (task boundary loaders)
Limitations: - Complex multi-method outputs (some methods partially supported) - Orbital extraction limited (no direct CUBE export) - Some advanced features (explicit correlation, local methods) not fully parsed - XML sidecar optional but required for full metadata
Test coverage: Molpro output fixtures for CCSD, CASSCF, optimization, frequency jobs
CLI Integration: orbitron inspect --molpro-task N and --molpro-kind freq filters for task-specific extraction.
Molcas/OpenMolcas (output files)
Capabilities: - Multi-configurational methods (CASSCF, RASSCF) - Perturbation theory (CASPT2, MS-CASPT2) - Optimization metadata (energy profiles, gradient convergence) - Frequency modes and thermochemistry - Task detection per module (SCF, RASSCF, CASPT2, OPT, FREQ) - Active space diagnostics (orbitals, spin, symmetry)
Implementation: io/pipelines/src/formats/molcas/mod.rs - Parser: Module scanner + per-module metadata - Detection: “Molcas” or “OpenMolcas” + module invocations - Canonical: Task summaries with RASSCF/CASPT2 diagnostics (extras.molcas) - Streaming: ✓ (module boundary loaders)
Limitations: - Limited electronic structure details compared to Gaussian/NWChem - Complex module structure (some modules partially supported) - Gradients and Hessians not fully extracted
MO surfaces via MOLDEN: Molcas writes orbital coefficients to sibling *.scf.molden, *.rasscf.molden, *.guessorb.molden, and *.mp2.molden files. The shared MOLDEN parser (next section) ingests these and the Sources Load button on RoleId::MolcasMolden attaches a complete electronic_structure (atoms + basis + MOs) to the active scene — Surfaces can render orbitals immediately without a separate FCHK / movecs companion.
Test coverage: Molcas output fixtures for RASSCF, CASPT2, optimization, frequency; qm_tests_molden_corpus_sanity walks every .molden under io/pipelines/tests/corpus/ and parses 17+ orbital-bearing files cleanly across SCF, RASSCF, MP2, and Guess flavors, including PbO with 6 spherical f-shells (pbo.scf.molden).
MOLDEN (*.molden)
Capabilities: - Portable text format emitted by Molpro, Molcas, ORCA, Turbomole, and many other QC packages - Atoms (AU or Angstrom), basis-set definition, MO coefficients (alpha + beta) - Spherical d/f/g flags ([5D], [7F], [9G]) with FCHK-compatible AO ordering - Pople-style sp combined shells (1 s + 3 p over shared exponents), read from the three-column primitive rows into sp_contraction_coefficients - Per-MO metadata: symmetry label, energy, spin, occupancy - NBO MOLDEN dialect (atom-block headers with optional second integer flag)
Implementation: io/pipelines/src/formats/molden/mod.rs - Parser: Section walker (SectionWalker) + per-section parsers (atoms / GTO / MO) - Output: MoldenData { atoms, basis: GaussianBasisSet, mos: Vec<MoldenMo> } - AO ordering matches fchk_conversion’s expected layout — no shell-column permutation needed (unlike NWChem’s .movecs which uses a different Cartesian d / f convention)
Sources Load handler (handle_molden_load in ui/shell/src/viewer_loop/runtime/redraw/panels/mod.rs): wires RoleId::MolproMolden and RoleId::MolcasMolden. Replaces the active scene’s basis with the MOLDEN basis (single source of truth), populates mo_coefficients_alpha/beta from the MO list, switches to the Orbitals tab, marks the row Loaded ✓.
Limitations: - Geometry-only / frequency-only MOLDEN flavors (*.geo.molden, *.freq.molden, [GEOCONV] / [N_FREQ] sections) are valid MOLDEN but out of scope; the parser returns IoPipelineError::Parse if you feed one in directly. The corpus test skips these by checking for [GTO] first.
Test coverage: 9 unit tests (acrolein and benzene SCF, header and non-finite-coordinate rejection, the bare [5D] flag, unparenthesised AU, Fortran D-format MO energies, sp combined shells, 1-based atom ids) + corpus walker (qm_tests_molden_corpus_sanity).
TREXIO (.h5, .hdf5, .trexio)
Capabilities:
- Content-detected HDF5 backend files, distinguished from DIRAC checkpoints by the TREXIO group layout
- Nuclear coordinates, labels and charges, including ECP core-electron recovery
- Total, alpha, and beta electron counts and the implied spin multiplicity
- Optional periodic cell and PBC flag
- Gaussian basis sets plus restricted or unrestricted MO energies, occupations, spin channels, and coefficients
- Bohr-to-Å conversion at the parser boundary
Implementation: io/pipelines/src/formats/trexio/
The reader uses the workspace’s existing HDF5 dependency instead of adding the TREXIO C binding and a second HDF5 stack. load_scene and load_trajectory probe HDF5-like extensions before text-handler dispatch. A trajectory request wraps a TREXIO scene as one frame because the format has no optimisation or vibrational trajectory group.
Limitations:
- Cartesian AO ordering, non-Gaussian bases, and non-unit
ao_normalizationare refused rather than guessed. - Determinants, RDMs, CSFs, amplitudes, Jastrow data, and integral groups are outside the current reader.
- TREXIO does not define vibrational-frequency or optimisation-trajectory groups, so those sections are empty by construction.
Test coverage: Official-library-generated molecular, periodic, ECP, and Gaussian-wavefunction fixtures; restricted and unrestricted MO paths; s/p/d/f/g ordering; malformed counts and non-finite values; scene and one-frame trajectory dispatch.
Quantum ESPRESSO (.out, .in, .xml, .xsf, .dat, .dos, .pdos_*, .bands, .bands.gnu, .UPF)
Capabilities: - Periodic DFT calculations (plane-wave basis) - SCF / relax / nscf outputs from .out text files - Input deck (.in) parsing — namelists &CONTROL / &SYSTEM, free-form ATOMIC_SPECIES / ATOMIC_POSITIONS / CELL_PARAMETERS blocks; lattice derivation for ibrav 0–14 (including centred orthorhombic, monoclinic, triclinic) plus alat / bohr / angstrom / crystal position units. - Structured XML (<prefix>.xml / data-file-schema.xml) parsing via roxmltree: atomic_structure, band_structure (per-k-point eigenvalues + occupations), Fermi level, total energy, convergence status, exit status, lsda / noncolin / spinorbit flags, creator program/version. - XSF reader handling both proper XSF (CRYSTAL / MOLECULE / ATOMS keyword files) and QE’s “Cube-as-xsf” flavour (pp.x output_format=6 writes Gaussian Cube content with an .xsf extension — routes through the existing cube parser). - DOS / PDOS / bands plot summaries from dos.x, projwfc.x, bands.x outputs (canonical extras + Sources Load summary toasts). - Phonon modes and dispersion from ph.x / q2r.x. - Bravais lattice + reciprocal lattice vectors. - SCF convergence history.
Implementation: - io/pipelines/src/formats/qe/mod.rs — handler + detection dispatch. - qe/canonical/builder.rs — .out text-output canonical builder (geometry, energetics, task list). - qe/input/ — .in namelist + geometry-block parser, split into namelists.rs, cards.rs, atoms.rs, cell.rs, and helpers.rs. 13 unit tests including ibrav volume invariants (5/7/9/10/11/12/14) and the user’s Si / Fe / SrTiO₃ / graphene / benzene fixtures. - qe/xml.rs — roxmltree-based data-file-schema.xml parser. 8 unit tests using real qm_tests/qe/ fixtures (gracefully skip when fixtures absent on CI). - qe/xsf.rs — proper-XSF + Cube-as-xsf dispatcher. 5 unit tests. - qe/dos.rs, pdos.rs, bands.rs — plot-data parsers (existing). - Streaming: ✓ for SCF/relax outputs; .dat and XML loaded fully.
Sources manifest (viewer/core/src/ui_state/sources/qe.rs): 10 roles. Required: QePrimary (.out), QeInput (.in). Optional/Advanced: QeXml (.xml), QeUpf (.UPF). Plot data: QeDos (.dos / *_dos.dat), QePdosTotal (.pdos_tot), QePdosAtomic (.pdos_atm*), QeBands (.bands / .bands.dat), QeBandsGnu (.bands.gnu), QeXsf (.xsf). Sets enforce_glob_stem_match = false because QE post-processing files use unrelated stems (UPFs by element, XML by &CONTROL prefix).
Limitations: - A dedicated QE Spectra panel that plots DOS / PDOS / band structure (mirroring the VASP equivalent) is future work — the parsers are in place but the rendering UI is not yet wired. - .UPF pseudopotentials are surfaced informationally in Sources but not parsed. - atomic_proj.xml (projwfc.x output) and the <prefix>.save/ binary checkpoint hierarchy are not parsed. - ibrav -12 / ±13 (centred monoclinic variants) are treated as primitive monoclinic with a warning that the centring isn’t expanded.
Test coverage: SCF / relax fixtures for benzene, graphene, silicon, SrTiO₃, Fe, FeO; XML / XSF / .in real-fixture tests via qm_tests/qe/ paths (gracefully skip on CI).
Canonical Integration: extras.qe.scf_total_energy_ry, relax_profile, dos_summary, bands_summary, pdos_summary.
Adding New Formats
The procedure lives in §6.1 and only there. It used to be written out again here and a third time in the Common Tasks appendix, and the three had drifted apart — the shortest of them omitted handler registration entirely, so following it produced a module nothing called.
After the parser works, remember this section: add the format to the capability matrix above and to the user guide’s data-support page, or it is supported and undiscoverable.
6.6 Sources Subsystem (companion-file manifests)
The Sources subsystem renders the Analysis → Sources sub-tab and powers companion-file detection for multi-file formats. Coverage today: NBO7, VASP, NWChem, ORCA, Gaussian, Molpro, Molcas, DIRAC, Quantum ESPRESSO. Every supported format declares a static manifest of “roles”: what each sibling file contributes. The runtime walks the active scene’s directory matching siblings against role patterns.
Type layer (viewer/core/src/ui_state/sources/): - manifest.rs — DetectedFormat enum, RoleId (one variant per role across all formats), CompanionRole (label + filename patterns + group + dependents), RoleGroup (Required / OptionalAdvanced / PlotData), RoleStatus (Loaded / Detected / Missing), and the filename_matches glob helper. Globs support four shapes: *X (ends-with, e.g. *.31), *X* (contains, e.g. *.pdos_atm*), X* (starts-with), and bare X (exact filename match like POSCAR). - nbo.rs — NBO_MANIFEST: archive .47 plus .31–.46 plot files, .nbo analysis text. - vasp.rs — VASP_MANIFEST: 14 roles (vasprun.xml, POSCAR, CONTCAR, OUTCAR, OSZICAR, INCAR, KPOINTS, POTCAR, DOSCAR, EIGENVAL, PROCAR, CHGCAR/CHG/PARCHG, XDATCAR, DYNMAT). - nwchem.rs — 7 roles: Output (*.out), Input (*.nw), *.movecs, *.hess, *.zmat, *.cube, *.civecs. - orca.rs: 7 roles: Output (*.out), Input (*.inp), converted *.molden.input, .gbw, .hess, *_trj.xyz, and .engrad. The manifest explains that .gbw must be converted with orca_2mkl before Orbitron can use its basis and MO coefficients. - gaussian.rs — 5 roles: Output (*.log/*.out), Input (*.gjf/*.com), *.fchk, *.chk, *.cube. - molpro.rs — 6 roles: Output, Input (*.inp/*.com), *.xml, *.log, *.molden, *.cube. - molcas.rs — 10 roles: Output, Input, *.opt.xyz, the orbital family (*.ScfOrb, *.RasOrb, *.GssOrb, *.LprOrb, *.Mp2Orb), *.molden, status. - dirac.rs — 4 roles: Output, Input, *.mol, *.h5. - qe.rs — 10 roles: Output (*.out), Input (*.in), *.xml, *.UPF, *.dos/*_dos.dat, *.pdos_tot, *.pdos_atm*, *.bands/*.bands.dat, *.bands.gnu, *.xsf. Sets enforce_glob_stem_match = false because QE post-processing files use unrelated stems (UPFs by element, XML by &CONTROL prefix). - mod.rs::manifest_for — dispatches a DetectedFormat to its manifest. New formats add one match arm here. - state.rs — SourcesState storing the detected format, scan root, last-detected-path cache, and per-role status vector.
Per-manifest stem-match flag: FormatManifest.enforce_glob_stem_match controls whether glob patterns additionally require sibling stems to match (or extend with a dot suffix) the primary file’s stem. True for every format except QE — QE’s pseudopotentials are named by element, the structured XML uses the &CONTROL prefix keyword (often differs from the .in filename), and post-processing files inherit whatever output stem the user configured dos.x / projwfc.x / bands.x to write.
Multi-format .out sniffing: ORCA, NWChem, Molpro, Molcas, DIRAC, QE, and Gaussian share .out (and Gaussian also uses .log). detect_format runs content sniffers in priority order: Molpro, Molcas, DIRAC, QE, ORCA, NWChem, then Gaussian. It caches the result on SourcesState.last_detected_path so the sniff does not repeat every redraw. .in files route directly to QE without a content sniff; .xml files are content-sniffed for the QE namespace to disambiguate them from VASP’s vasprun.xml, which is caught earlier by path_is_vasp_primary.
Runtime layer (ui/shell/src/sources_runtime.rs): - refresh_sources(ui_state) runs at the top of every panel pass and is also called once after the menu_bar handler (which clobbers temp_ui_state.analysis to preserve File→Open menu actions). Idempotent — early-returns when format and scan_root haven’t changed. - detect_format checks the NBO workspace first (an .47 may be loaded into memory without being the active scene), then falls back to the active scene path: .47 extension → NBO7; canonical VASP filename → VASP. - update_role_statuses walks the manifest, calls loaded_path_for_role to recognise files already in the workspace, and otherwise scans sibling files in the run directory. - Mode-aware sibling matching in resolve_role_status: glob patterns (*.31) require stem-match against the primary file (so a fixtures-dir of u2oplot.* and uo2-test.* doesn’t cross-pollinate). Exact filenames (POSCAR) skip the stem constraint since each VASP file has a canonical name and can’t collide with another role. Stem-match also accepts extended stems — siblings whose stem starts with <primary_stem>. qualify, which handles Molcas’ *.scf.molden / *.rasscf.molden task-suffix convention without permitting cross-run matches (the dot anchor protects against acrolein2.scf.molden matching acrolein.out).
Panel + actions: - ui/shell/src/panels/analysis/sources.rs renders one row per role with a status icon, the role’s label/description tooltip, and one of three button affordances (Reload / Load / Add file…). VASP POSCAR/CONTCAR rows also expose a Compare button that emits SourcesAction::OverlayCompanion. - ui/shell/src/viewer_loop/runtime/redraw/panels/mod.rs::handle_sources_action dispatches role loads: - NBO roles stage into NboWorkspace via load_archive_contents / add_supporting_file_contents. - VASP roles (most) re-run begin_loading_path so the canonical pipeline auto-collects siblings; analysis state is mirrored back to temp_ui_state to survive the end-of-frame sync clobber. - CHGCAR/CHG/PARCHG register as orbital datasets and switch to the Surfaces tab. - XDATCAR loads via the trajectory dispatcher (see formats/loaders/trajectory.rs::load_trajectory). - OverlayCompanion pushes the file as a scene overlay through register_overlay_from_path (no primary-scene swap).
Adding Sources support for a new format: 1. Add the RoleId variants to viewer/core/src/ui_state/sources/manifest.rs. 2. Create viewer/core/src/ui_state/sources/<format>.rs with the manifest constant. 3. Wire manifest_for in mod.rs. 4. Extend sources_runtime::detect_format and loaded_path_for_role to recognise the format’s primary file and any in-workspace state. 5. Branch handle_sources_action for any role-specific load behavior; default is begin_loading_path (swap primary scene). Volumetric / trajectory roles need their own dispatch.
6.7 Orbitron Scene JSON Format
This subsection describes the Orbitron Scene JSON format — a stable, human-readable representation of a molecular scene.
The format is produced by SceneGraph::to_json() in Rust and Scene.to_json() in the Python bridge, and consumed by SceneGraph::from_json() and Orbitron.load_json().
The web viewer does not read it. Scene JSON is a Rust and Python interchange format for inspection and hand-editing; the viewer takes MessagePack scene bytes or an .orbpack bundle, which is one input path with a version and provenance instead of three without. Python converts for you — export_html and the notebook widget both accept a scene, a JSON string, or a bundle.
Envelope
Every Orbitron scene JSON file starts with the envelope fields:
{
"format": "orbitron-scene",
"version": "1.0",
...scene fields...
}| Field | Type | Value | Required |
|---|---|---|---|
format |
string | "orbitron-scene" |
Yes |
version |
string | "1.0" |
Yes |
The envelope is optional for input — from_json() also accepts bare snapshot JSON (without format/version). It is always present in output.
Scene Fields
The scene fields are flattened into the top-level object alongside the envelope fields.
atoms (required)
Array of atom records.
"atoms": [
{
"id": 0,
"position": [1.2, 0.0, -0.5],
"atomic_number": 6,
"mass_number": null,
"formal_charge": 0,
"properties": {}
}
]| Field | Type | Description |
|---|---|---|
id |
integer | Unique atom identifier (u64) |
position |
[f32, f32, f32] | Cartesian coordinates in Ångström |
atomic_number |
integer (1–118) | Element atomic number (H=1, C=6, …) |
mass_number |
integer or null | Isotopic mass (null = natural abundance) |
formal_charge |
integer (i8) | Formal charge (typically −4 to +4) |
properties |
object | Arbitrary key–value properties (see Properties) |
explicit_bonds (optional)
Array of explicit bond records. If omitted, bonds are inferred from interatomic distances by the viewer.
"explicit_bonds": [
{
"id": 0,
"atoms": [0, 1],
"order": "Single",
"properties": {}
}
]| Field | Type | Description |
|---|---|---|
id |
integer | Unique bond identifier (u64) |
atoms |
[u64, u64] | IDs of the two bonded atoms (ordered) |
order |
string or null | "Single", "Double", "Triple", "Aromatic", "Unspecified", or {"Other": N} |
properties |
object | Arbitrary key–value properties |
metadata (optional)
"metadata": {
"name": "Caffeine",
"source": "PubChem CID 2519",
"tags": {
"gaussian:last_energy_hartree": "-679.514",
"gaussian:optimization_converged": "true"
}
}| Field | Type | Description |
|---|---|---|
name |
string or null | Human-readable molecule name |
source |
string or null | Provenance / file path / URL |
tags |
object (string→string) | Arbitrary metadata key–value pairs |
unit_cell (optional)
Present for periodic systems (crystals, slabs, etc.).
"unit_cell": {
"a": [5.43, 0.0, 0.0],
"b": [0.0, 5.43, 0.0],
"c": [0.0, 0.0, 5.43],
"periodic": [true, true, true]
}| Field | Type | Description |
|---|---|---|
a |
[f32, f32, f32] | Lattice vector a in Ångström |
b |
[f32, f32, f32] | Lattice vector b in Ångström |
c |
[f32, f32, f32] | Lattice vector c in Ångström |
periodic |
[bool, bool, bool] | Which axes are periodic [a, b, c] |
digest (optional)
A Blake3 content hash. Produced automatically by to_json(); ignored on input but preserved for cache invalidation.
"digest": {
"hash": "3a8f..."
}Properties
Both atoms and bonds carry an optional properties object. In Rust it is an Arc<IndexMap<PropertyId, serde_json::Value>> (core/backbone/src/scene/types.rs), so keys are strings and values are bare JSON — there is no tagged wrapper:
"properties": {
"partial_charge": -0.31,
"residue_seq": 42,
"is_backbone": true,
"residue_name": "ALA"
}Any serde_json::Value round-trips, including arrays and nested objects.
Complete Minimal Example
{
"format": "orbitron-scene",
"version": "1.0",
"atoms": [
{"id": 1, "position": [0.0, 0.0, 0.0], "atomic_number": 6,
"mass_number": null, "formal_charge": 0, "properties": {}},
{"id": 2, "position": [1.54, 0.0, 0.0], "atomic_number": 6,
"mass_number": null, "formal_charge": 0, "properties": {}}
],
"explicit_bonds": [
{"id": 0, "atoms": [1, 2], "order": "Single", "properties": {}}
],
"metadata": {"name": "Ethane", "source": null, "tags": {}}
}Atom ids are 1-based — every parser assigns them that way, matching each format’s native numbering and the CLI’s select / measure contract, and io/pipelines/tests/atom_id_base.rs asserts no parser emits an atom at id 0. Bond atoms entries are atom ids and follow the same convention.
Loading in Different Contexts
Rust:
use orbitron_backbone::SceneGraph;
let scene = SceneGraph::from_json(json_str)?;
let json = scene.to_json()?;WASM (JavaScript / TypeScript):
// A bundle or a MessagePack scene, told apart by their contents
await viewer.loadScene("./caffeine.orbpack");
// Or bytes you already hold
await viewer.loadSceneBytes(new Uint8Array(buffer));To show scene JSON in the browser, convert it first:
orbitron.export_html(json_string, "caffeine.html")Python:
from orbitron import Orbitron
orb = Orbitron()
scene = orb.load("caffeine.xyz")
# Export to JSON
json_str = scene.to_json()
# Reload from JSON
scene2 = orb.load_json(json_str)
# Inline Jupyter display (calls to_json() internally)
scene # displays interactive 3D viewer in notebookStability
- The
formatandversionfields are reserved for future backward-compat negotiation. - The
1.0schema is stable: adding new optional fields is allowed; removing or renaming fields requires a version bump. - The binary
.binformat (bincode) is not stable — struct changes break it. Use JSON for long-lived files or cross-language exchange.