Format Capability Matrix
What each of chematic's 15 supported file formats actually does, in Rust, Python, and WASM, as of v0.18.0 — read/write coverage, streaming, coordinate units, connectivity handling, round-trip fidelity, lossy operations, parse limits, and known limitations.
See also: rdkit-migration.md for a feature-by-feature
Supported/Partial/Not-supported breakdown against RDKit, and
language-bindings.md for cross-language API
mapping (function names, return shapes, None/null/Err divergences).
This page describes what exists today. It does not claim "full support" for any format — each row states exactly what is implemented and names the gaps.
Support matrix
Read (R) / Write (W) per language. A dash means that operation is not exposed in that language. "—" in the WASM columns for CIF means CIF has zero WASM exposure at all, not an oversight in this table.
| Format | Rust R | Rust W | Python R | Python W | WASM R | WASM W |
|---|---|---|---|---|---|---|
| SMILES | Y | Y | Y | Y (Mol.smiles property) |
Y | Y |
| SMARTS | Y | — (matching, not writing) | validation only | — | Y (match) | — |
| MOL/SDF | Y | Y | Y | Y (Mol.to_mol_block()/.to_mol_block_2d()) |
Y | Y |
| PDB | Y | Y | Y | Y (Mol.to_pdb(coords)) |
Y | Y (ConformerHandle.get_conformer_pdb(idx)) |
| mmCIF | Y | Y | Y | Y | Y | Y |
| CIF (small-molecule) | Y | Y | Y | — | — | — |
| PQR | Y | Y | Y | Y | Y | Y |
| XYZ | Y | Y | Y | — | Y | Y |
| Extended XYZ | Y | Y | Y | Y | Y | Y |
| QCSchema | Y | Y | Y | Y | Y | — (validation only, no writer) |
| ORCA input | Y | Y | Y | Y | Y | Y |
| ORCA output | Y | — (no writer exists in Rust) | Y | — | Y | — |
| Gaussian Cube | Y | Y | Y (via VolumetricGrid) |
Y (via VolumetricGrid) |
Y | Y |
| OpenDX | Y | Y (strict + _lossy) |
Y (via VolumetricGrid) |
Y (via VolumetricGrid, strict + _lossy) |
Y | Y (strict + _lossy) |
| LAMMPS data | Y | Y | Y | Y | Y | Y |
| LAMMPS dump/trajectory | Y | Y | Y | Y | Y | Y |
Notes on the cells above that need qualification:
- SMILES Python write / MOL-SDF Python write: exposed as
Molobject methods/properties (Mol.smiles,Mol.to_mol_block()/.to_mol_block_2d()), not as freeformats.rsfunctions in the same shape as the read side (from_smiles,from_mol_block, etc.). Both directions exist; the binding shape just differs from the read side — seelanguage-bindings.mdfor the exact names. - QCSchema WASM write:
qcschema_validate_atomic_input/qcschema_validate_atomic_resultvalidate a JSON document; the only WASM writer present isto_qcschema_molecule_json(forQcMolecule, notAtomicInput/AtomicResult).
Per-format detail
SMILES
- Rust:
chematic_smiles::{parse, write, canonical_smiles, parse_cxsmiles, write_cxsmiles, parse_smi_file, write_smi_file, random_smiles}. - Python:
from_smiles,from_cxsmiles,is_valid_smiles,parse_smi_file,write_smi_file. - WASM:
parse_smiles/write_smiles,mol_block_from_smiles,smiles_array_to_sdf,parse_cxsmiles_json,normalize_cxsmiles,is_valid_smiles. - Streaming:
parse_smi_file/write_smi_filematerialize fully. Separately,chematic-mol'ssmiles_tablemodule (SmilesRecordReader/SmilesRecordWriter, a different sub-format — delimited SMILES-table files with name/property columns, not plain.smi) implements a trueBufRead-backed streamingIterator. It is not exposed to Python/WASM and is out of scope for the "15 formats" table this page otherwise tracks; noted here only because it bears directly on any "no format in this codebase streams" claim. - Coordinate units: N/A — SMILES encodes graph topology and stereo parity, not coordinates. CXSMILES can carry optional 2D coordinates as an extension.
- Connectivity: native — bonds are the primary content of the format.
- Round-trip: graph-canonical, not byte-identical (whitespace/atom order
are not preserved).
canonical_smiles()is not guaranteed to be a safe dedup/cache key today — see the Known Limitations section below. - Lossy operations: none inherent to the format itself.
- Parse limits: no
*ParseLimitstype exists for SMILES. - Known limitations:
canonical_smiles()has a documented residual — isolated/simple E/Z double bonds can still produce two different, both-valid canonical strings for the same molecule in ~1 in 18 stereo-bearing molecules (measured on a 5,000-mol ChEMBL corpus; see README's "Known Limitations" section for the exact figures). Do not use it as a dedup key without accounting for this.
SMARTS
- Rust:
chematic_smarts::{parse_smarts, find_matches, find_matches_with_rings, ...}. - Python:
is_valid_smarts— validation only. Full parse/substructure matching is exposed throughMol.find_matches()/Mol.has_substructure()(on the molecule object, not as aformats.rs-style free function) — do not read this as "SMARTS has no Python support," it means the shape of the binding differs from the other formats in this table. - WASM:
smarts_match_atoms,smarts_match_atoms_with_chirality,match_smarts_smiles,parse_cxsmarts_json. - SMARTS is a query language, not a molecule storage format — streaming, coordinate units, connectivity, round-trip, and lossy-operation columns don't apply in the same sense as the other 14 formats.
- Parse limits: no
*ParseLimitstype exists.
MOL/SDF
- Rust read:
parse_mol,parse_mol_with_coords,read_mol_with_diagnostics,parse_mol_v3000*,SdfReader,SdfFileReader,SdfRecordReader. - Rust write:
write_mol,write_mol_with_conformer[_checked],write_sdf*,write_mol_v3000*. - Python:
from_mol_block,from_mol_block_with_coords,from_mol_block_with_diagnostics,parse_sdf_with_coords,from_mol_v3000[_with_coords|_with_diagnostics]. - WASM:
mol_from_v3000_block,mol_from_sdf_block,to_mol_block,to_mol_v3000_block,sdf_to_smiles_json,sdf_to_records_json,sdf_from_records_json,mol_block_stereo_diagnostics_json,mol_v3000_stereo_diagnostics_json,mol_block_coords_json. - Streaming:
SdfFileReader<R: BufRead>is a true I/O-streamingIterator(does not require the whole file in memory up front).SdfReader/SdfRecordReaderare lazy iterators over an already-loaded&str(do not eagerly collect every record into aVec, but do require the full text in memory). Python and WASM bindings materialize (no streaming reader is bound in either language). - Coordinate units: Ångström, per the Ctab standard.
- Connectivity: native Ctab bond table.
- Round-trip: semantic, not byte-identical —
write_molregenerates a Ctab from theMolecule's own perceived bonds/coordinates, not a copy of the original text. - Lossy operations: none named beyond the stereo-write scope below.
- Square-planar stereo (
@SP1/@SP2/@SP3): read automatically on every MOL/SDF parse. Write is opt-in only, via exactly 3 functions —write_mol_with_conformer_checked,write_mol_v3000_with_conformer_checked,write_sdf_record_with_conformer_checked. Do not describe MOL/SDF writing in general as "square-planar supported" — the plainwrite_mol/write_sdfpath does not perceive or emit it. - Parse limits: no
*ParseLimitstype exists for MOL/SDF.
PDB
- Rust:
chematic_3d::{PdbAtom, parse_pdb_atoms, pdb_to_molecule, write_pdb}— this format lives inchematic-3d, notchematic-mol; the one exception among these 15. - Python:
from_pdb(delegates tochematic_3d) for reading;Mol.to_pdb(coords)for writing. - WASM:
mol_from_pdb,pdb_coords_jsonfor reading. Writing is exposed as a method, not a free function:ConformerHandle.get_conformer_pdb(idx)returns conformeridxas a PDB string (ornullifidxis out of range), delegating tochematic_3d::write_pdbinternally — this is real write capability, just shaped around an existing conformer handle rather than awrite_pdb_json(mol, coords)-style free function. - Coordinate units: Ångström (PDB standard).
- Connectivity:
pdb_to_moleculeperceives bonds from geometry (distance-based), unlike most of the other formats in this table, which either carry an explicit bond table or never infer one. - Round-trip: not characterized in this pass — no documented round-trip guarantee found; treat as best-effort.
- Lossy operations: none named.
- Parse limits: no
*ParseLimitstype exists.
mmCIF
- Rust:
chematic_mol::{parse_mmcif, parse_mmcif_with_limits, write_mmcif, MmcifAtomRecord, MmcifError, MmcifParseLimits, MmcifResult}. - Python:
parse_mmcif,write_mmcif. - WASM:
mol_from_mmcif,mmcif_coords_json,mmcif_to_json,write_mmcif_json. - Streaming: no streaming reader type exists for mmCIF.
- Coordinate units: always orthogonal Ångström (unlike small-molecule CIF's fractional coordinates).
- Connectivity: no bond table, ever — this module never infers bonds.
- Round-trip: lossy on read for the
label_*/auth_*tag-pair collapse —auth_*is preferred when both are present, both are treated as one logical field, exceptlabel_seq_id(kept as its own field since it is legitimately.for non-polymer atoms even whenauth_seq_idis normal).write_mmcifwrites the single stored value into both columns, so a read→write round trip loses the distinction if the source file'slabel_*andauth_*genuinely disagreed. Unrecognized_atom_sitecolumns are collected intounhandled_columnsrather than silently dropped, but are not necessarily re-emitted verbatim on write. - Lossy operations: the
label_*/auth_*collapse above. - Parse limits:
MmcifParseLimits { max_input_bytes, max_atoms, max_line_len }.
CIF (plain, small-molecule)
- Rust:
chematic_mol::{parse_cif, write_cif, CifError, CifResult, UnitCell}, plus#[cfg(feature = "crystal")]-gated periodic-structure variants. - Python:
parse_cif— read only, nowrite_cifbinding exists inchematic-py. - WASM: none. Zero WASM exposure — confirmed by grep, not by omission from this table.
- Coordinate units: fractional cell coordinates converted to orthogonal Ångström on read (IUCr convention).
- Connectivity: no bond table at all — CIF's
_atom_siteloop carries only positions; this module returns atoms with no bonds. - Round-trip: symmetry expansion is not performed on the plain (non-
crystal-feature) path — only atoms literally listed in_atom_site_*are returned (effectively P1 treatment). Seedocs/crystal_scope.mdfor thecrystal-feature periodic-structure variants. - Lossy operations: symmetry-operation information is not applied/round-tripped on the plain path.
- Parse limits: no
*ParseLimitstype exists for plain CIF.
PQR
- Rust:
chematic_mol::{parse_pqr, parse_pqr_with_limits, write_pqr, infer_element, PqrAtomRecord, PqrError, PqrParseLimits, PqrResult}. - Python:
parse_pqr,write_pqr,infer_element. - WASM:
mol_from_pqr,pqr_coords_json,pqr_to_json,write_pqr_json,pqr_infer_element. - Coordinate units: Ångström.
- Connectivity: no bond table, none inferred (unlike PDB, which does infer from geometry).
- Element: PQR has no element column in the format;
Elementis inferred from the atom name viainfer_element, a documented heuristic matching classic PDB-parser convention for blank element columns. - Round-trip: not characterized beyond the element-inference note above.
- Lossy operations: none named beyond element inference (which is a read-side reconstruction, not a write-side loss).
- Parse limits:
PqrParseLimits { max_input_bytes, max_atoms, max_line_len }.
XYZ / Extended XYZ
Two distinct functions share the name parse_xyz in different crates —
disambiguate by crate, not by name alone:
chematic_3d::parse_xyz -> Result<(Molecule, Coords3D), XyzError>— infers bonds by distance, returns a fullMolecule.chematic_mol::parse_xyz -> Result<XyzFrame, XyzError>— noMolecule, no bonds; the Extended-XYZ-superset document type. Also hasparse_extxyz/parse_extxyz_all/write_extxyz/write_xyz,XyzReader/ExtxyzReader/ExtxyzWriter.- Python:
from_xyzuseschematic_3d's bond-inferring version.from_extxyz/from_extxyz_all/to_extxyzusechematic_mol's non-bond version. - WASM:
mol_from_xyz/to_xyzusechematic_3d's version.mol_from_extxyz/extxyz_frame_json/to_extxyz_jsonusechematic_mol's version. - Streaming:
XyzReader/ExtxyzReaderare lazy iterators over an already-loaded&str(same category asSdfReader, not aBufRead-based reader). NoBufRead-backed streaming type exists for XYZ. Python/WASM materialize. - Coordinate units: Ångström (standard XYZ/extended-XYZ convention).
- Connectivity:
chematic_3d::parse_xyzinfers bonds by distance;chematic_mol::parse_xyz/parse_extxyznever do (noMoleculeis even produced on that path). - Round-trip: not characterized beyond the two-crate split above.
- Lossy operations: none named.
- Parse limits: no
*ParseLimitstype exists for either crate's XYZ path.
QCSchema
- Rust:
chematic_mol::{QcMolecule, AtomicInput, AtomicResult, parse_qcschema_molecule, write_qcschema_molecule, parse_atomic_input, write_atomic_input, parse_atomic_result, write_atomic_result, chematic_to_qc_molecule, qc_molecule_to_chematic}. - Python: same names, routed through Python's stdlib
jsonmodule rather than a hand-mapped dict. - WASM:
mol_from_qcschema_molecule,qcschema_molecule_coords_json,to_qcschema_molecule_json,qcschema_validate_atomic_input,qcschema_validate_atomic_result. - Coordinate units:
QcMolecule.geometryis explicitly Bohr (a0) in Rust; Python and WASM bindings convert to Ångström for convenience. - Connectivity:
connectivity: Option<Vec<(usize, usize, f64)>>is the only one of these 15 formats where bonds are ever an optional, spec-native part of the document — used if present, never fabricated if absent. - Round-trip: open extensibility bags (
extras/keywords/protocols/native_files/properties) round-trip losslessly viaBTreeMap, kept distinct from anunknown_fieldsbag for genuinely undocumented top-level keys. - Lossy operations: none named — every numeric leaf is rejected if non-finite rather than silently coerced.
- Parse limits: no
*ParseLimitstype exists (size limits, if any, are whatever the caller orserde_jsonimpose).
ORCA input
- Rust:
chematic_mol::{parse_orca_input, write_orca_input, OrcaInput, OrcaInputError, OrcaBlock, OrcaCoords, OrcaAtom}. - Python:
parse_orca_input,write_orca_input. - WASM:
mol_from_orca_input,orca_input_coords_json,orca_input_to_json,write_orca_input_json. - Coordinate units: Ångström.
- Connectivity: no bond perception anywhere in this module.
- Round-trip: lossless preservation of unknown
%block ... endblocks, including nested sub-blocks. - Lossy operations: none named.
- Parse limits: no
OrcaParseLimitstype exists (unlike mmCIF/PQR/Cube/OpenDX).
ORCA output
- Rust:
chematic_mol::{parse_orca_output, OrcaOutput, OrcaOutputError, OrcaTermination, OrcaOptConvergence}— read-only, no writer, in all 3 languages. - Python:
parse_orca_output. - WASM:
orca_output_to_json. - Coordinate units: Ångström.
- Connectivity: no bond perception.
- Semantics:
terminationandoptimization_convergenceare two independently-reported fields —ORCA TERMINATED NORMALLYalone does not imply a requested geometry optimization converged; check both. - Parse limits: no
*ParseLimitstype exists.
Gaussian Cube
- Rust:
chematic_mol::{parse_cube, parse_cube_with_limits, write_cube, CubeError, CubeFileReader, CubeParseLimits}, plus the sharedVolumetricGridtype. - Python: no free functions — only via the
VolumetricGridpyclass:VolumetricGrid.from_cube()(staticmethod),.to_cube(),.values_3d. - WASM:
mol_from_cube,cube_grid_json,write_cube_json, plus typed-arraycube_values_f64,cube_shape_u32. - Streaming:
CubeFileReader<R: BufRead>streams the input reading only (reduces peak parse-time memory) — the returnedVolumetricGrid.valuesis always one fully-materializedVec<f64>. This is a different sense of "streaming" thanSdfFileReader/LammpsDumpReader(Cube is a single-dataset format, so there is nothing to iterate across). - Coordinate units:
GridUnits::{Bohr, Angstrom}explicit tag; values are never silently normalized between them. The sign of the first-axis voxel count in the header disambiguates Bohr vs. Ångström (documented against a real spec source). - Connectivity: no bonds, ever.
GridAtom.chargeis effective nuclear charge, not partial/formal charge. - Multi-dataset files: typed-rejected, not silently truncated —
CubeError::MultiDatasetUnsupported. Cube is single-dataset-only in this codebase today. - Round-trip: not byte-identical (no claim of verbatim footer/comment preservation is made in the module docs).
- Lossy operations: none named on the read/write path itself (the multi-dataset case is rejected, not silently truncated).
- Parse limits:
CubeParseLimits { max_input_bytes, max_atoms, max_grid_points }.
OpenDX
- Rust:
chematic_mol::{parse_opendx, parse_opendx_with_limits, write_opendx, write_opendx_lossy, OpenDxError, OpenDxParseLimits}. - Python: no free functions — only via the
VolumetricGridpyclass:.from_opendx(),.to_opendx(),.to_opendx_lossy(). Nomol_from_opendx— OpenDX has no atom section at all. - WASM:
opendx_grid_json,write_opendx_json,write_opendx_lossy_json, plus typed-arrayopendx_values_f64,opendx_shape_u32. - Coordinate units: OpenDX has no in-file unit tag — every parsed
grid is tagged
Angstromby convention (an assumption about the dominant real-world producer, not information recovered from the file itself). - Connectivity: no bonds — OpenDX carries no atom section.
- Round-trip: footer boilerplate is regenerated on write, not preserved byte-for-byte.
- Lossy operations, explicitly named:
write_opendxfails closed (OpenDxError::NonAngstromUnits) on aBohr-tagged grid — it will not silently write a wrong-unit file.write_opendx_lossyis the explicit opt-in Bohr→Ångström conversion. It rescalesorigin/axesonly — it never rescalesvalues.- Both writers refuse (
OpenDxError::AtomsNotSupported) any grid with non-empty atoms — there is no lossy-atom-dropping path. - Parse limits:
OpenDxParseLimits { max_input_bytes, max_grid_points }— nomax_atoms(the format has none).
LAMMPS data
- Rust:
chematic_mol::{parse_lammps_data, write_lammps_data, LammpsData, LammpsDataError, LammpsAtom, LammpsAtomStyle, LammpsBond, LammpsBox, LammpsMass, LammpsVelocity}. - Python:
parse_lammps_data,write_lammps_data(plain dict). - WASM:
lammps_data_to_json,write_lammps_data_json. atom_stylehandling:atom_stylecannot be inferred from the file and must be passed explicitly.atomic/charge/molecular/fullare supported; anything else maps toLammpsAtomStyle::Otherand is rejected withLammpsDataError::UnsupportedAtomStyle— there is no best-effort guess, becausechargeandmolecularrows are both genuinely ambiguous 6-field rows by column count alone.- Connectivity: raw index topology (atom-type/bond indices), not
chemically perceived — this is a standalone document type, not integrated
with
Molecule. - Section handling: 4 sections are typed (
Masses/Atoms/Velocities/Bonds); everything else is preserved byte-for-byte verbatim inunparsed_sections(an orderedVec<(String, String)>, not aHashMap). The writer emits typed sections first, then opaque sections in their original relative order — interleaving between typed and opaque sections is not preserved, though each opaque section's own content is. - LAMMPS "Type Labels" extension: explicitly unsupported, fails
closed — any section whose name ends in
"Type Labels"is rejected rather than silently mishandled. - Coordinate units: none stated in-file — LAMMPS units come from the
simulation's own
unitscommand, not this file type; chematic does not infer or convert them. - Round-trip: opaque sections round-trip verbatim; typed sections round-trip through the typed representation (not guaranteed byte-identical, e.g. whitespace).
- Parse limits: no
LammpsParseLimitstype exists.
LAMMPS dump/trajectory
- Rust:
chematic_mol::{parse_lammps_dump_frame, write_lammps_dump_frame, write_lammps_trajectory, box_bounds_to_true, true_to_box_bounds, LammpsDumpFrame, LammpsDumpReader, LammpsDumpError}. - Python:
parse_lammps_dump_frame,parse_lammps_dump_all(materializes the whole trajectory — does not exposeLammpsDumpReader's streaming; a disclosed scope choice, already stated in CHANGELOG),write_lammps_dump_frame,write_lammps_trajectory,box_bounds_to_true,true_to_box_bounds; pyclassLammpsDumpFrame(.column(),.cartesian_positions()). - WASM:
lammps_dump_frame_to_json_str,lammps_trajectory_to_json(also materializes, does not stream — same disclosed choice),write_lammps_dump_frame_json,write_lammps_trajectory_json,lammps_dump_cartesian_positions_json, plus typed-arraylammps_dump_rows_f64,lammps_dump_cartesian_positions_f64. - Streaming:
LammpsDumpReader<R: BufRead>is a true streamingIteratorover anyBufRead— reads and yields one frame at a time without holding the whole trajectory in memory. Together withSdfFileReader, this is one of twoBufRead-backed streaming readers among these 15 Rust formats, and the only one whose Python/WASM bindings both deliberately materialize instead of streaming (a disclosed trade-off, not an oversight — see CHANGELOG[0.18.0]/[0.17.0]). - Box bounds: triclinic box-bounds↔true-box conversion
(
box_bounds_to_true/true_to_box_bounds) independently verified against LAMMPS's own documentation.LammpsBoxis reused from thelammps_datamodule; only this module performs the bound↔true conversion. cartesian_positions(): resolvesx y z(passthrough) orxs ys zs(triclinic-aware scaled transform), but deliberately does not resolvexu yu zu("unwrapped" coordinates) — returnsNoneif neither of the first two triples is present, even if unwrapped coordinates are, since conflating "current position" with "unwrapped position" would mislead. Callers use.column("xu")directly for that case.- Divergence between JSON and typed-array WASM bindings: when a frame
has no resolvable coordinate columns,
lammps_dump_cartesian_positions_jsonreturns JSONnull, butlammps_dump_cartesian_positions_f64returnsErr— aFloat64Arrayhas nonullrepresentation. Both are doc-commented; this is disclosed, not accidental. Seelanguage-bindings.md. - Coordinate units: not stated in-file (same as LAMMPS data).
- Parse limits: no
*ParseLimitstype exists.
Formats that never fabricate bonds
mmCIF, PQR, ORCA (input and output), Gaussian Cube, OpenDX, LAMMPS data, and
plain CIF never infer or fabricate a bond table — they either have no bond
concept in the format at all, or (PQR specifically) have an element-inference
step that is documented separately from connectivity. PDB and
chematic_3d::parse_xyz are the exceptions among these 15: both infer bonds
from 3D geometry (distance-based), which is a documented, disclosed choice,
not a default you should assume applies elsewhere.
Cross-reference
For the exact per-language function signatures, return-value shapes, and
None/null/Err divergence points summarized above, see
language-bindings.md. For how these facts map to
RDKit's equivalent APIs, see rdkit-migration.md.