Skip to content

Semantic model API

The semantic API is an explicit, loss-aware layer for structures that cannot be represented safely as an ordinary Molecule.

Bounded nucleic-acid documents

NucleicAcidDocument is a separate versioned contract with schema marker chematic.nucleic-acid.v1. It preserves one or more ordered DNA/RNA strands, explicit residue IDs, base and known modification identity, ribose or deoxyribose identity, atom references, linear 3'-to-5' phosphodiester linkage references, and JSON annotations. Every declared atom belongs to exactly one residue, and each linkage must connect adjacent residues in one strand.

The first slice does not infer a sequence from a molecular graph, create a molecular graph, support cross-strand/cyclic/branched linkage topology, or accept unknown modification semantics. Those cases return a typed NucleicAcidError. NucleicAcidCommand permits bounded annotation edits and explicit residue-identity replacement while leaving topology and atom ownership immutable.

Rust uses NucleicAcidDocument::from_json[_str], validate, apply, and the *_with_limits variants. Python and WASM/Node expose nucleic_acid_validate_json and nucleic_acid_apply_json_command. Both return chematic.nucleic-acid-validation.v1 envelopes containing either the normalized lossless document or an error with stable code, path, and message fields.

Default limits are 1 MiB input, 64 strands, 10,000 residues, 100,000 declared atoms, 512 atom references per residue, 10,000 linkages, 10,000 annotation entries, 256 KiB of annotation JSON, and 256-byte IDs. Rust callers may lower these through NucleicAcidLimits.

Stability contract

The schema marker chematic.semantic.v1 is the public interchange contract. SemanticModel::validate must succeed before edits or expansion. An R-group alternative is never selected implicitly. An R-group alternative may contain one or more [*] linkage placeholders; placeholders are paired with attachment_atoms in source order and are never copied into the expanded graph. An R-group may contain typed nested_groups; nested choices are validated recursively, selected explicitly, and expanded in stable parent before-child order. A polymer repeat is expandable only when it has an explicit repeat count and either a repeat_smiles value with exactly two [*] linkage placeholders or two explicit repeat_endpoint_atoms. A null repeat_count is a valid unselected editing state and can be set with the typed SetPolymerRepeatCount command; expansion rejects it until selected. ClearPolymerRepeatCount returns a selected unit to that explicit non-expandable state without flattening it into an ordinary molecule.

When polymer_units[].end_groups is non-empty it must contain exactly two SMILES fragments, ordered [left, right]. Each fragment must contain exactly one [*] marker with one neighbor. The marker is replaced by a bond to the corresponding polymer attachment atom; the mapping exposes the stable keys <unit>.end_group_left and <unit>.end_group_right. Other Markush/polymer topologies remain explicitly rejected with SemanticError. For typed interchange, polymer_units[].end_group_definitions accepts exactly two PolymerEndGroup records (id, smiles) in left/right order; their IDs are used as stable source_to_expanded keys. The legacy end_groups and typed form cannot be supplied together. s_groups exposes typed S-group membership and optional repeat-unit and polymer-linkage references. A linkage has stable ID, two distinct source atom references, and a validated bond order; unknown S-group kinds remain explicit strings and are not silently expanded. SetSGroupKind edits a group by stable ID. Biomolecule payloads are explicitly rejected until a typed API exists. Expanded provenance maps S-group and linkage IDs to their source atom indices, alongside the existing atom, Markush, polymer, and end-group mappings.

SemanticModel::apply returns a new model for command-style editing. Expansion returns ExpandedSemantic, including a source_to_expanded mapping for undo, re-edit, and provenance display. Unsupported or ambiguous input returns a typed SemanticError; callers must not treat it as a best-effort molecule. Expansion is bounded by SemanticExpansionLimits: expand uses the default 10,000 repeats per polymer unit and 100,000 output atoms, while trusted callers can use expand_with_limits with an explicit budget. A limit failure is a typed SemanticError::ExpansionLimit and is checked before each fragment is added to the output graph. ExpandedSemantic::contract returns the exact base graph captured at expansion time; it does not attempt to infer a base graph from an arbitrary edited expansion. Binding JSON includes the same provenance-backed graph as contracted_smiles, so callers can restore the source graph without guessing. The stable JSON contract can be decoded with SemanticModel::from_json and expanded through the Python and WASM/Node semantic_expand_json APIs. Those bindings also expose semantic_expand_json_with_limits with explicit max_atoms and max_repeat_count arguments; the legacy function continues to use the same finite defaults. Markush alternatives are selected explicitly via semantic_apply_json_command; no alternative is inferred. ReplaceRGroupAlternatives edits the allowed substituent set by stable group ID and clears any prior selection, requiring a fresh explicit selection. ClearRGroupAlternative restores the unselected Markush state and provides a lossless semantic contraction boundary.

CDXML document contract

CdxmlDocument preserves the original XML and exposes multi-page/page-object summaries, including presentation-only objects. CdxmlPage::bounding_box provides the optional page dimensions as a typed left, top, right, bottom tuple and rejects malformed values. CdxmlObject::kind classifies known atom, bond, group, arrow, text, caption, and drawing objects while retaining an Unsupported kind for unknown tags. CdxmlDocument::diagnostics reports unknown presentation tags with page/object locations while retaining their raw XML. CdxmlObject::transform and CdxmlObject::z_order provide typed access to common Matrix and ZOrder attributes and reject malformed values. CdxmlObject::text_style provides typed access to common text/caption font, size, and alignment attributes; malformed or non-finite sizes are rejected. Unknown attributes remain available in the object attribute map. CdxmlEdit applies bounded page-attribute or opaque-object replacements and re-parses the result before returning it; apply_json_edit is the binding-neutral command boundary. ReplaceObjectPath addresses a multiline nested object by its parent-to-child sibling path (for example, [0, 1] is the second child of the first page-level object); the JSON summary also includes the serialized object kind. Unknown attributes and all untouched objects are retained in write() output. Python exposes parse_cdxml_document_json and edit_cdxml_document_json; WASM/Node expose the corresponding cdxml_document_json and edit_cdxml_document_json functions. The binding JSON marker is chematic.cdxml-document.v1; diagnostics are included in that JSON summary. Full ChemDraw presentation semantics remain outside the contract and are reported rather than silently interpreted.