Format Capability Matrix
Read/write coverage and important limits for Rust, Python and WASM. The matrix is a starting point; per-format notes identify coordinate, connectivity and round-trip boundaries.
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.
Common format conversion bridge
The Python package provides convert_format(text, input_format, output_format)
and the WASM package provides convert_common_format(text, input_format,
output_format) for explicit conversion between the most interoperable molecular formats:
SMILES, MOL/SDF (V2000), MOL V3000, MOL2, CML, ChemicalJSON, MolJSON, CDXML,
PDB, XYZ, PDBQT, and Gaussian input. Extensions are accepted as aliases (for
example .mol2, smi, and com). PDB/XYZ/PDBQT output requires 3D
coordinates; graph-only conversions do not claim to preserve format-specific
metadata. Unsupported formats fail before conversion with a ValueError.
The WASM bridge intentionally exposes the topology-only subset (SMILES, MOL, MOL V3000, MOL2, CML, ChemicalJSON, MolJSON, and CDXML) so that JavaScript and Python share a predictable core contract. Use the existing coordinate APIs for PDB/XYZ/PDBQT in browser workflows.
This is intentionally a bounded interoperability layer, not a claim of broad
Open Babel replacement. The format-specific APIs remain available when
coordinates, metadata, multiple records, or domain-specific options must be
preserved. The narrower production-format comparison and its strict win
criteria are documented in openbabel-file-io.md.
MOL2 → MOL2 is a source-preserving exception in CLI, Python, and WASM: one validated record is returned exactly, including coordinates, partial charges, typing, comments, and opaque sections. Python rejects coordinate, charge, name, or comment overrides on this path. Cross-format conversion continues to use the bounded graph/coordinate bridge described above.
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 format matrix this page otherwise tracks; noted here because it bears directly on any "no format in this codebase streams" claim. - Streaming limits:
TdtReaderOptionsandSmilesReaderOptionsbound physical lines and retained record state; TDT also bounds tags per record. - 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-oriented, not byte-identical (whitespace/atom order
are not preserved).
canonical_smiles()is not a universal dedup/cache key; see compatibility scope. - Lossy operations: none inherent to the format itself.
- Parse limits:
SmilesParseLimitscontrols input bytes, atom count, and bond count;parseapplies finite safe defaults andparse_with_limitsaccepts a stricter policy. - SMILES tables:
parse_smi_file_with_limitsacceptsSmiFileParseLimits { max_input_bytes, max_line_bytes, max_records }; the defaultparse_smi_filepath uses finite defaults. - Streaming SMILES tables:
SmilesReaderOptionsbounds line bytes, yielded records, and columns per row; limit violations are typedSmilesTableErrorresults. - Known limitation: coupled E/Z systems can make canonical text
unsuitable for identity. Use the fail-closed
canonical_smiles_stable_key()within its documented domain; see compatibility scope.
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; coordinate and round-trip columns do not apply.
- Parse limits:
SmartsParserConfigsets recursive$()depth (default 8, clamped to 1–16). Useparse_smarts_with_configto choose it explicitly.
PDBQT
- Rust:
chematic_mol::{parse_pdbqt, parse_pdbqt_with_limits, write_pdbqt, PdbqtError, PdbqtParseLimits}. - Coordinate units: Ångström.
- Connectivity: no bond perception; rigid-body PDBQT only.
- Parse limits:
PdbqtParseLimitsbounds input bytes, physical line length/count, and ATOM/HETATM records. The existing parser uses finite defaults.
Checked conversion reports (initial adapter)
Rust convert_with_report / convert_with_report_with_limits, Python
convert_with_report (dict) / convert_with_report_json (JSON string), WASM
convert_with_report_json, and CLI convert-with-report share
chematic.conversion-report.v1. Supported routes are MOL2 → MOL2/SMILES/CJSON
and CJSON → CJSON/SMILES/MOL2 (smi aliases plain SMILES). Existing conversion
APIs keep their graph/metadata contracts.
report = chematic.convert_with_report(source, "mol2", "smiles")
assert report["status"] == "refused"
assert report["output"] is None
lossy = chematic.convert_with_report(source, "mol2", "smiles", policy="allow_loss")
# Inspect lossy["observations"] before using lossy["output"].
chematic convert-with-report --input-format mol2 --output-format smiles \
--policy allow_loss --input ligand.mol2 --output report.json
strictis the Rust policy variant and Python/CLI default. Cross-format MOL2 → SMILES/CJSON and CJSON → SMILES/MOL2 refuse withoutput: nullbecause source information is lost or unverified. CLI writes the refusal JSON and exits nonzero.allow_lossreturns parseable candidate output with detected losses and uncertainty. A graph mismatch isunverified; it cannot claim preservation. A candidate that fails to parse is refused even under this policy.preserve_sourcerequires MOL2 → MOL2 or CJSON → CJSON. All three policies retain exact source bytes on these routes after bounded structural validation. The scope isexact_source_bytes;source_semantics: opaque_retainedexplicitly excludes interpreted/editable chemical semantics. It is not a graph gate.- Observations identify a source field group, status, stage and reason in
source record zero. Coordinate/partial-charge/type/name/substructure/bond
metadata losses and opaque sections are reported before graph projection.
The aggregate
source_textloss also covers comments and unparsed columns. - Candidate output is reparsed. SMILES emitted atom order or CJSON source-order
atom arrays establish correspondence. Counts, mapped elements, aromaticity, projected formal charges and bond
orders are checked.
candidate_atom_mapping/candidate_bond_mappingcarry source IDs and zero-based source/candidate indices only if this check passes. These mappings refer to the candidate even when strict policy refuses to emit it.verification_scopedefines the checked fields. - MOL2 formal-charge interpretation beyond
N.4, coordinate/extension stereo, and source implicit-hydrogen assignment remain unverified. SMILES hydrogen counts are explicitly inferred under the graph valence model; CJSON does not emit an inferred hydrogen count. A verified projected graph does not establish chemical equivalence to the full source record. - MOL2 → CJSON carries source x/y/z components and projected formal charges.
coordinates: preservedrequires verified graph correspondence and bitwise equality of reparsed f64 coordinates, including signed zero. Decimal spelling, physical dimensionality and coordinate-derived stereo are outside this check.projected_formal_chargesis checked separately from the unverified source formal-charge interpretation. Source atom types/names/IDs, substructures, comments and opaque sections remain reported losses. Complete declared partial-charge datasets now emit separately aspartialCharges.<source method>and are verified bit for bit through mapped atoms. Values are not computed, rounded into formal charges or filled when missing. Empty/NO_CHARGESmethod declarations do not produce a dataset. The parsed molecule name emits asproperties.nameand is checked on reparse. The source method label is retained without verifying its chemistry. Atom names/types/IDs and unsupported metadata remain losses. - Invalid input, unsupported routes/policies and limits are errors (Python
ValueError, WASM throw, CLI nonzero without a report). CLI--outputis always a JSON report destination, never a standalone molecule destination.
Default Rust/Python input and report/output limits are 16 MiB with 10,000
reader atoms; WASM input is 1,000,000 bytes. This new SMILES route additionally
caps projection at 512 atoms because the current writer traverses recursively.
The cap does not apply to same-format source retention or CJSON/MOL2 output. JSON
size checks include escaping and envelope overhead. The MOL crate enables serde_json's
float_roundtrip feature: a finite f64 emitted as decimal JSON can be read
back without the previous 1 ULP rounding error. This also applies to existing
Rust/Python/WASM CJSON readers. Integral bond orders emit JSON integer
numbers, while aromatic order 1.5 remains fractional; this avoids the observed
Open Babel 3.2.1 CJSON reader converting floating integer orders into zero.
Paired development checks record both
engines' losses and targeted cross reads. No cross-engine superiority is established.
Immutable ChemicalJSON records
Rust parse_cjson_record / parse_cjson_record_with_limits, CjsonRecord,
and write_cjson_record retain the exact original UTF-8 source. Python/WASM
parse_cjson_record_json expose chematic.cjson-record.v1 inspection;
roundtrip_cjson_record validates and returns the source. The record is
immutable: opaque references are retained without editing or interpretation.
The strict local profile accepts absent chemicalJson or integer version 1,
atomic numbers, optional formal charges/isotopes/3D coordinates, bond
indices/orders, properties.name and root partialCharges datasets. Present arrays must have matching lengths and correct types.
Duplicate decoded JSON keys, self/duplicate bonds, dangling indices and
unsupported orders are rejected. Supported order values are exactly 1, 1.5,
2, 3 and 4; 4 is an explicit local alias for aromatic bond 1.5. Atom aromaticity
and coordinate stereo are not perceived. The legacy parse_cjson API keeps
its permissive behavior; strict validation is additive.
Root partialCharges must be an object of complete finite numeric arrays, one
value per atom; properties must be an object and its optional name a string.
These fields are validated even during same-format retention. The inspection
API exposes name and partial_charge_datasets; Rust has corresponding getters.
Method keys remain source declarations, with no charge calculation or model
validation. atoms.partialCharges remains opaque and is not conflated with the
root dataset representation.
Unknown fields, including other properties and stereo extensions, are listed
as sorted JSON Pointer paths in opaque_fields. Whole opaque subtrees are
represented by their root paths. They survive same-format source retention;
CJSON → SMILES reports their loss and separately reports typed name/charge
dataset losses. That route also reports coordinate loss,
defaulted charges/isotopes/orders where absent, inferred hydrogen counts and
unverified stereo. Candidate verification checks mapped elements, aromatic
flags, formal charges, isotopes and bond orders. CJSON mapping source_id
values are zero-based array indices; opaque user IDs are not interpreted.
Strict CJSON → SMILES refuses even when the projected graph verifies.
import json
record = json.loads(chematic.parse_cjson_record_json(source))
assert chematic.roundtrip_cjson_record(source) == source
assert chematic.convert_with_report(source, "cjson", "cjson")["output"] == source
lossy = chematic.convert_with_report(source, "cjson", "smiles", policy="allow_loss")
Checked CJSON → MOL2 accepts explicit 3D coordinates and at most one declared
partial-charge dataset. It writes shortest round-trip decimal coordinates and
charges, reparses the candidate and checks exact f64 bits (including signed
zero/subnormals) through verified graph correspondence. properties.name and
the charge-method label are checked separately on reparse. Missing datasets
emit NO_CHARGES with no partial-charge columns; missing coordinates are never
filled. Multiple datasets, unrepresentable method tokens and names with
boundary whitespace, control characters, empty text or section markers refuse
even under allow_loss. Exact CJSON retention remains available for valid
records refused by this adapter.
MOL2 atom IDs/names, element-only types and SMALL header are generated;
charged atom rows use placeholder substructure 1 LIG. Hybridization and
force-field types are not inferred from chemistry. The sole nonzero formal
charge representation is +1 nitrogen via N.4. Other nonzero formal charges
and all specified isotopes are reported dropped; they disable projected graph
verification and candidate mappings. Partial charges are never substituted for
formal charges. Aromatic bond tokens retain the local projection, without
perceiving atom aromaticity. Opaque fields and stereo remain lost/unverified.
Strict policy refuses this cross-format route even when the candidate verifies.
report = chematic.convert_with_report(source, "cjson", "mol2", policy="allow_loss")
# Inspect status and observations; valid source data may still be refused.
Rust/Python record APIs use CjsonParseLimits defaults (256 MiB input,
1,000,000 atoms); WASM record APIs cap input at 1,000,000 bytes and 10,000
atoms, with 16 MiB inspection output. Checked conversion uses the smaller
16 MiB / 10,000 atom defaults described above. JSON tree limits apply to
opaque fields too. Existing convert_format remains a graph bridge for CJSON;
use the record or checked API for source retention.
MOL2 (Tripos)
- Rust:
chematic_mol::{parse_mol2, parse_mol2_with_limits, write_mol2}retains the published graph/editable-record contract. Exact-source inspection uses the additiveMol2SourceRecord,parse_mol2_source_record,parse_mol2_source_record_with_limits, andwrite_mol2_source_recordAPIs.Mol2SourceAtomRecordandMol2SourceBondRecorddescribe their metadata; the sharedMol2Error/Mol2ParseLimitstypes remain compatible. - Python/WASM:
parse_mol2_record_jsonexposes the sharedchematic.mol2-record.v1inspection schema;roundtrip_mol2_recordvalidates and returns the original source. Rust exposes the same schema throughMol2SourceRecord::to_json(). - Coordinate units: Ångström.
- Immutable source-profile connectivity: native MOL2 bond section.
amprojects to single;arto aromatic. Unknown bond tokens reject instead of becoming single. Duplicate IDs/edges, self bonds, dangling references, mismatched counts, nonfinite numbers, and multiple records/duplicate core sections reject. - Immutable source-profile charges: partial charges are validated separately from formal charge.
The graph recognizes
N.4as +1; it does not round partial charges or implement general formal-charge inference/UNITY semantics. Optional charge columns remain optional in the record API, with the source charge-method header retained.NO_CHARGESmeans numerical columns are not a claim of assigned partial charges. - Round-trip: immutable
Mol2SourceRecordretains the exact source and typed atom/bond metadata, including source IDs, names, atom types, substructure references, partial charges, and original bond tokens. Extra sections are listed as opaque; source retention does not prove their interpretation. Geometry-derived stereochemistry is not perceived by this reader. - Graph-only write:
write_mol2emitsNO_CHARGES, simplified atom types, and zero numerical charge placeholders; it does not compute Gasteiger charges or retain source metadata. Missing coordinates still default to zero on this compatibility API. The published writer encodes formal charges with UNITY attributes; isotope, atom-map and stereo annotations are not generally encoded. Use the immutable source API for exact source retention. - Parse limits:
Mol2ParseLimitsbounds input bytes, physical line length/count, section count, atom records, and bond records. The existing parser uses finite defaults.
CML
- Rust:
chematic_mol::{parse_cml, parse_cml_strict, parse_cml_with_limits, parse_cml_strict_with_limits, write_cml, CmlError, CmlParseLimits}. - Strict boundary:
parse_cml_strictis opt-in; it requires a non-empty molecule, balanced XML-ish elements, exactly one root element, and rejects unterminated quoted attributes, comments, CDATA sections, and processing instructions. The historicalparse_cmlpath remains lenient for compatibility. - Coordinate units: Ångström in returned 2D coordinates.
- Connectivity: native CML bond elements.
- Parse limits:
CmlParseLimitsbounds input bytes, physical line length/count, XML element count, and atom/bond elements. The existing parser uses finite defaults.
MolJSON
- Rust:
chematic_mol::{parse_moljson, parse_moljson_with_limits, write_moljson, MolJsonError, MolJsonParseLimits}. - Parse limits:
MolJsonParseLimitsbounds JSON input bytes/depth, array/string resources, and atom/bond records. The existing parser uses finite defaults.
ChemicalJSON
- Rust:
chematic_mol::{parse_cjson, parse_cjson_with_limits, write_cjson, CjsonError, CjsonParseLimits}. - WASM:
mol_from_cjsonprovides the bounded topology-handle entry point; coordinates and CJSON metadata remain on the format conversion path. - Parse limits:
CjsonParseLimitsbounds JSON input bytes/depth, array/string resources, and atom/bond records. The existing parser uses finite defaults and rejects numeric type/range truncation.
CDXML
- Rust:
chematic_mol::{parse_cdxml, parse_cdxml_with_limits, parse_cdxml_all, parse_cdxml_all_with_limits, write_cdxml, CdxmlError, CdxmlParseLimits, CdxmlParseOptions}. - Document API:
CdxmlDocumentsupports bounded multi-page summaries, exact-source writing, and loss-preserving page/object command edits. Opaque presentation objects are retained; arbitrary ChemDraw presentation interpretation/rendering is outside the compatibility claim. - Parse limits:
CdxmlParseLimitsbounds input bytes, physical line bytes/count, attribute bytes, atoms, bonds, and fragments. Existing parsers use finite defaults; option and limit policies can be combined withparse_cdxml_all_with_options_and_limits.
MOL/SDF
- Rust read:
parse_mol,parse_mol_with_coords,read_mol_with_diagnostics,parse_mol_v3000*,SdfReader,SdfFileReader,SdfRecordReader,SdfDataset. - 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],iter_sdf,iter_sdf_batched, andSdfDataset. - WASM:
mol_from_v3000_block,mol_from_sdf_block,to_mol_block,to_mol_v3000_block,roundtrip_mol_v3000_block(preserves opaque SGROUP metadata and COLLECTION groups),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).SdfBatchReader<R>is the bounded pull-based variant: each item contains at most the configured batch size, preserves source order, retains rejected records, and can be cancelled at a batch boundary. Pulling the next item is the backpressure contract; no background queue is created.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 exposes file-backediter_sdfand boundediter_sdf_batched; WASM bindings still materialize SDF input. - 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:
SdfParseLimitsbounds input bytes, physical line bytes, individual record bytes, and yielded record count; useSdfFileReader::with_limits,SdfRecordReader::with_limits, orparse_sdf_with_limitsfor an explicit policy. The existingparse_sdf,SdfReader, andSdfRecordReader::newuse finite defaults. - Indexed access:
SdfDataset::openscans delimiter lines once and stores byte ranges plus titles, not parsed molecules.len,metadata,get,record_text, andread_rangethen provide bounded-memory random access. Python exposes the same boundary asSdfDataset; malformed records retain their index and fail only when requested.
PDB
- Rust:
chematic_3d::{PdbAtom, parse_pdb_atoms, parse_pdb_atoms_strict, pdb_to_molecule, write_pdb}— this format lives inchematic-3d, notchematic-mol. - Python:
from_pdb(lenient) andfrom_pdb_strict(fixed-column validation) for reading; both delegate tochematic_3d;Mol.to_pdb(coords)for writing.protein_from_pdb_jsonexposes the shared amino-acid-only model/chain/residue/atom projection. - WASM:
mol_from_pdb(lenient),mol_from_pdb_strict(fixed-column validation), andpdb_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. - Protein projection: Rust
chematic_mol::project_protein_from_pdb, Pythonprotein_from_pdb_json, and WASMprotein_from_pdb_jsonpreserve model, chain, residue, atom, coordinate, occupancy, B-factor, alternate-location, and insertion-code fields and expose one-letter sequences. The view is intentionally amino-acid-only: unrecognized residues, water, ligands, ions, and nucleic acids are excluded rather than guessed. - 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:
PdbParseLimitsbounds input bytes, physical line length, ATOM/HETATM records and MODEL records. Useparse_pdb_atoms_with_limitsfor typed resource-limit errors.parse_pdb_atoms_strictadditionally rejects invalid fixed-column serial, residue sequence and XYZ fields.
mmCIF
- Rust:
chematic_mol::{parse_mmcif, parse_mmcif_with_limits, write_mmcif, MmcifAtomRecord, MmcifError, MmcifParseLimits, MmcifResult}. - Python:
parse_mmcif,write_mmcif, andprotein_from_mmcif_json. - WASM:
mol_from_mmcif,mmcif_coords_json,mmcif_to_json,write_mmcif_json, andprotein_from_mmcif_json. - Protein projection: Rust
chematic_mol::project_protein_from_mmcifand the Python/WASM JSON functions use the same amino-acid-only hierarchy and residue policy as the PDB projection above. This is not a complete biological-assembly/entity API. - 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:
CifParseLimits { max_input_bytes, max_line_bytes, max_tokens, max_atoms }; useparse_cif_with_limitsfor an explicit policy. The existingparse_cifparser uses finite defaults.
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:
XyzFileReader<R: BufRead>is a true file-backed streaming iterator, andXyzBatchReader<R: BufRead>adds bounded pull-based batches with input order, cancellation, and a versioned progress manifest.XyzReader/ExtxyzReaderremain lazy iterators over an already-loaded&str.ExtxyzFileReader<R: BufRead>provides the same file-backed frame boundary for Extended XYZ, andExtxyzBatchReader<R: BufRead>provides the corresponding bounded batch contract. Python/WASM still materialize XYZ frames. - 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: both XYZ implementations have finite defaults. The
chematic_3d::XyzParseLimitsAPI bounds input bytes, atom count, and physical line length for the bond-inferring parser;chematic_mol::XyzParseLimitsadditionally bounds frame count for multi-frame input. Their respectiveparse_xyz_with_limits,parse_xyz_all_with_limits, andparse_extxyz_with_limitsaccept an explicit policy; default single/all-frame parsers use finite defaults.
KET (Ketcher JSON)
- Rust:
chematic_mol::{parse_ket, parse_ket_with_limits, parse_ket_3d, parse_ket_3d_with_limits, write_ket, write_ket_3d, KetError, KetParseLimits}. - Parse limits:
KetParseLimits { max_input_bytes, max_atoms, max_bonds }; default parsing uses finite defaults and the explicit APIs return typedKetError::ResourceLimitfailures. - Python/WASM: existing KET entrypoints use the bounded default parser path; structured Rust errors are surfaced as the binding's normal value/JS error representation.
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}. - Parse limits:
QcSchemaParseLimitsbounds input bytes, JSON nesting depth, array entries, and string bytes.parse_qcschema_molecule_with_limits,parse_atomic_input_with_limits, andparse_atomic_result_with_limitsaccept an explicit policy; the existing parsers use finite defaults. - 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 an optional explicit bond table — 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.
ORCA input
- Rust:
chematic_mol::{parse_orca_input, parse_orca_input_with_limits, write_orca_input, OrcaInput, OrcaInputError, OrcaInputParseLimits, 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:
OrcaInputParseLimitsbounds input bytes, physical line length/count, keywords, blocks, block bytes, and coordinate atoms. The existingparse_orca_inputparser uses finite defaults.
ORCA output
- Rust:
chematic_mol::{parse_orca_output, parse_orca_output_with_limits, OrcaOutput, OrcaOutputError, OrcaOutputParseLimits, 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:
OrcaOutputParseLimitsbounds input bytes, physical line bytes/count, geometry frames/atoms, and frequency values. The existing parser uses finite defaults.
Gaussian input/log
- Rust:
chematic_mol::{parse_gjf, parse_gjf_with_limits, write_gjf, parse_gaussian_log, parse_gaussian_log_with_limits, GaussianLogResult, GaussianParseLimits, GaussianError}. - Parse limits:
GaussianParseLimitsbounds input bytes, physical line length/count, section count, and atom count for both GJF and log parsing. - Coordinate units: Ångström.
- Connectivity: no bond table is inferred.
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, parse_lammps_data_with_limits, write_lammps_data, LammpsData, LammpsDataError, LammpsDataParseLimits, 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:
LammpsDataParseLimitsbounds input bytes, physical line length, header counts, typed-section rows, opaque-section bytes, and section count. Useparse_lammps_data_with_limits; the existing parser uses finite defaults.
LAMMPS dump/trajectory
- Rust:
chematic_mol::{parse_lammps_dump_frame, parse_lammps_dump_frame_with_limits, write_lammps_dump_frame, write_lammps_trajectory, box_bounds_to_true, true_to_box_bounds, LammpsDumpFrame, LammpsDumpReader, LammpsDumpParseLimits, LammpsDumpError}. - Python:
parse_lammps_dump_frame,parse_lammps_dump_all(materializes the whole trajectory — does not exposeLammpsDumpReader's streaming; a documented scope choice),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 documented Rust formats, and the only one whose Python/WASM bindings both deliberately materialize instead of streaming (a disclosed trade-off, not an oversight). - 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:
LammpsDumpParseLimits { max_input_bytes, max_line_bytes, max_atoms_per_frame, max_columns, max_frames }bounds both single-frame input and the streaming reader; useparse_lammps_dump_frame_with_limitsorLammpsDumpReader::with_limits.
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 exceptions: 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.
Integration into v1.1.0 keeps its published editable Mol2Record and
parse_mol2_record/write_mol2_record APIs intact, including UNITY formal-charge
attributes and editable typed sections. The new immutable Mol2SourceRecord
profile is separate: checked conversion and JSON inspection use its stricter
validation and N.4-only charge projection. Legacy editable/graph parsing is not
claimed to enforce this stricter profile. Earlier v1.0.20 evidence remains
historical and is not relabeled as a v1.1.0 artifact result.
Current-main integration validation is recorded in
validation/results/main-integration-2026-10-11/summary.json.
The earlier v1.0.20 development measurements remain historical evidence.