CLI Reference
The chemrof command-line tool provides utilities for working with
chemical structure data in the chemrof framework.
Installation
uv sync
This installs the chemrof command from the project's entry point.
Commands
chemrof convert
Convert one or more chemical input strings (SMILES or InChI, auto-detected) into chemrof-compliant records.
chemrof convert [OPTIONS] INPUTS...
Arguments:
| Argument | Description |
|---|---|
INPUTS |
One or more SMILES or InChI strings (required). Quote ions: "[Ca+2]" |
Options:
| Option | Default | Description |
|---|---|---|
--format, -f |
yaml |
Output format: yaml, json, or owl |
--enrichers, -e |
(none) | Comma-separated list of enricher sources |
--classes, -c |
(none) | Target chemrof classes (implies --autochain) |
--autochain |
false |
Generate interlinked dependent entities |
--siblings |
false |
Also generate the input's siblings: every species of an element, or every stereoisomer of a molecule (cannot be combined with --classes/--autochain) |
--max-siblings |
64 |
With --siblings, the most stereoisomers to generate for one molecule |
--chemont-source |
(none) | Local ChemOnt labels source for --enrichers chemont: DuckDB, Parquet, or TSV/ZST |
--chemont-dictionary |
(auto) | ChemOnt dictionary TSV/Parquet when it is not bundled with the source |
Input format detection
The converter auto-detects the input format:
- Starts with
InChI=→ parsed as InChI - Otherwise → parsed as SMILES
Non-standard InChI with /s3 (racemic) automatically triggers --autochain
with --classes RacemicMixture.
Output formats
YAML (default) — one document per entity, or multi-document YAML for autochain output:
chemrof convert CCO
id: INCHIKEY:LFQSCWFLJHTTHZ-UHFFFAOYSA-N
name: C2H6O
type: chemrof:SmallMolecule
smiles_string: CCO
inchi_string: InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3
empirical_formula: C2H6O
molecular_mass: 46.0419
is_organic: true
JSON — same structure, JSON-encoded:
chemrof convert CCO --format json
OWL — OWL Functional Syntax ontology via linkml-data2owl. Types with
owl.template annotations (MonoatomicIon, Enantiomer, RacemicMixture)
produce EquivalentClasses axioms. Others produce annotation assertions:
chemrof convert "[Ca+2]" --format owl
EquivalentClasses(INCHIKEY:... ObjectIntersectionOf(
chemrof:MonoatomicIon
ObjectSomeValuesFrom(chemrof:has_element chemrof:Ca)
DataHasValue(chemrof:elemental_charge "2"^^xsd:integer)
))
Autochain
The --classes option generates interlinked entity graphs. Requesting
RacemicMixture generates the chirality-agnostic form, both enantiomers,
and the racemic mixture entity — all with relationship slots populated.
# Generate full racemate graph from stereo-neutral alanine
chemrof convert "CC(N)C(=O)O" --classes RacemicMixture --format json
# Enantiomers only (no mixture entity)
chemrof convert "CC(N)C(=O)O" --classes Enantiomer
# From a specific enantiomer — generates agnostic + mirror + mixture
chemrof convert "C[C@@H](N)C(=O)O" --classes RacemicMixture
# Racemic InChI auto-triggers autochain
chemrof convert "InChI=1/C3H7NO2/c1-2(4)3(5)6/h2H,4H2,1H3,(H,5,6)/t2-/s3"
Chemical salts: Salt SMILES (multi-fragment, opposite charges) are auto-detected and decomposed into cation + anion + salt entities:
# NaCl → Na+ entity, Cl- entity, NaCl salt entity
chemrof convert "[Na+].[Cl-]" --format json
# Sodium acetate
chemrof convert "[Na+].CC([O-])=O" --format json
# Explicit --classes flag
chemrof convert "[Ca+2].[Cl-].[Cl-]" --classes ChemicalSalt --format json
Tautomers: Enumerate tautomeric forms and cross-link via tautomer_of:
# 2-hydroxypyridine / 2-pyridinone tautomers
chemrof convert "Oc1ccccn1" --classes Tautomer --format json
v1 limitation: autochain only supports single-stereocenter molecules. For more
than one stereocenter use --siblings, below.
Siblings
--siblings generates the other members of the input's family. Any member
of the family gives the whole family.
Atoms — every species of the element: the neutral atom, the naturally occurring isotopes, and the monoatomic ions and isotope-labelled forms that ChEBI has a class for.
# Starting from an element, an ion or an isotope gives the same family
chemrof convert "[Fe]" --siblings
chemrof convert "[Fe+3]" --siblings
# ... with CHEBI ids where ChEBI has a class
chemrof convert "[Fe]" --siblings --enrichers chebi --format json
The family of iron includes Fe (UnchargedAtom), 54Fe/56Fe/57Fe/58Fe
(Isotope), Fe+2/Fe+3 (AtomCation) and 57Fe+2/57Fe+3
(FullySpecifiedAtom). The ions and labelled forms come from ChEBI, so a charge
state ChEBI lacks (say Fe+7) is not generated unless it is the input itself.
Molecules — the stereo-agnostic parent plus every stereoisomer of it.
Chiral stereoisomers are Enantiomers (linked to the parent by
enantiomer_form_of); achiral ones — meso forms, cis/trans isomers — are
Stereoisomers. Each pair of mirror images is grouped in a RacemicMixture.
# R,R / S,S pair, its racemate, and the meso form
chemrof convert "CC(O)C(C)O" --siblings
# Four centers give 16 isomers; cap the output
chemrof convert "OCC(O)C(O)C(O)C=O" --siblings --max-siblings 8
2^n isomers are possible for n stereo elements, so output is capped at
--max-siblings (a warning says when that truncates). Salts and molecules
without stereo elements have no siblings and are returned as is, with a warning.
For one stereocenter the output is the same graph as --classes RacemicMixture.
Enrichers
Enrichers pull additional data from external databases after RDKit computes the structural properties. Pass a comma-separated list:
chemrof convert CCO --enrichers pubchem
chemrof convert CCO --enrichers pubchem
chemrof convert "[Fe+3]" --enrichers chebi
| Source | Status | What it adds |
|---|---|---|
pubchem |
Working | Preferred IUPAC name and PubChem CID (via InChIKey lookup) |
chemont |
Working | Ordered ChemOnt/ClassyFire tree classes in classified_by (via local lookup store) |
openclatura |
Working | Systematic IUPAC_name derived locally from the structure, no network (via openclatura; needs pip install 'chemrof[openclatura]') |
chebi |
Working (atoms) | For atoms, monoatomic ions and isotopes: replaces id with the CHEBI id and sets name (offline, from a bundled table). Other entities are left unchanged |
wikidata |
Stub | Will resolve Wikidata QIDs via SPARQL |
ChemOnt setup and usage
ChemOnt enrichment uses the large ClassyFire/ChemOnt Zenodo release. For repeated CLI lookups, prepare an indexed DuckDB store once:
# Download the required Zenodo files into a temporary directory and build DuckDB
chemrof prepare-chemont-from-zenodo chemont.duckdb
# Keep the downloaded Zenodo files for reuse or inspection
chemrof prepare-chemont-from-zenodo chemont.duckdb \
--download-dir /tmp/chemrof-chemont-downloads \
--overwrite
Then run the ChemOnt enricher against the local store:
chemrof convert CCO --enrichers chemont --chemont-source chemont.duckdb
The output includes the ordered ChemOnt path in classified_by:
classified_by:
- CHEMONTID:0000000
- CHEMONTID:0004603
- CHEMONTID:0000323
- CHEMONTID:0000129
- CHEMONTID:0000286
For repeated use, set the source once:
export CHEMROF_CHEMONT_SOURCE="$PWD/chemont.duckdb"
chemrof convert CCO --enrichers chemont
OWL output maps each classified_by value to a SubClassOf axiom:
chemrof convert CCO \
--enrichers chemont \
--chemont-source chemont.duckdb \
--format owl
SubClassOf(<http://identifiers.org/inchikey/LFQSCWFLJHTTHZ-UHFFFAOYSA-N> CHEMONTID:0000286)
Parquet output is also available for scan-heavy workflows:
chemrof prepare-chemont-from-zenodo chemont-parquet --format parquet
chemrof convert CCO --enrichers chemont --chemont-source chemont-parquet
If the Zenodo files are already downloaded, build a local store directly:
chemrof prepare-chemont \
/tmp/chemrof-chemont-downloads/classyfire_dedup_inchikey_smiles.enriched.tsv.zst \
/tmp/chemrof-chemont-downloads/chemont_dictionary.tsv \
chemont.duckdb \
--overwrite
Auto-classification
The converter inspects the parsed molecule and picks the most specific chemrof class:
| Structure | chemrof type |
|---|---|
Single atom, positive (e.g. [Ca+2]) |
AtomCation |
Single atom, negative (e.g. [Cl-]) |
AtomAnion |
Single atom, neutral (e.g. [He]) |
UnchargedAtom |
Single atom, neutral, mass number given (e.g. [13C]) |
Isotope |
Single atom, charged, mass number given (e.g. [13C+]) |
FullySpecifiedAtom |
Multi-fragment salt (e.g. [Na+].[Cl-]) |
ChemicalSalt |
Multi-atom, all stereocenters assigned (e.g. C[C@@H](N)C(=O)O) |
Enantiomer |
Multi-atom, positive (e.g. [NH4+]) |
MolecularCation |
Multi-atom, negative (e.g. CC([O-])=O) |
MolecularAnion |
Multi-atom, neutral (e.g. CCO) |
SmallMolecule |
chemrof enrich-reactions
Adds thermodynamic estimates to the reactions in a ChEMROF document, using eQuilibrator's component contribution method.
chemrof enrich-reactions reactions.yaml
The input may be a Collection with an entities list, a bare list of entities,
or a single Reaction. Non-reaction entities are left untouched, but they are
used as participant records, so a document that carries both molecules and
reactions resolves more participants than one with reactions alone.
Requires the optional thermo extra, and the first run downloads a compound
cache of several gigabytes:
pip install 'chemrof[thermo]'
| Option | Default | Meaning |
|---|---|---|
--enrichers |
equilibrator |
Comma-separated reaction enrichment sources |
--p-h |
7.5 |
pH to estimate at |
--ionic-strength |
0.25 |
Ionic strength in mol/L |
--p-mg |
3.0 |
Negative log of free Mg²⁺ activity; use 14 for magnesium-free |
--temperature |
298.15 |
Temperature in kelvin |
--reversibility-index |
off | Also estimate the reversibility index |
--format |
yaml |
yaml or json |
--output |
stdout | Write to a file instead |
The defaults match eQuilibrator's own, which are its physiological defaults.
Each reaction gains is_balanced and one has_thermodynamic_estimate entry per
quantity, carrying the conditions it was computed at:
is_balanced: true
has_thermodynamic_estimate:
- quantity_type: standard_transformed_gibbs_free_energy_change
value: -29.641753
standard_error: 0.304278
unit: kJ/mol
p_h: 7.5
ionic_strength: 0.25
p_mg: 3.0
temperature: 298.15
estimation_method: component_contribution
source: eQuilibrator (equilibrator-api 0.8.1)
Estimates are appended, so re-running at different conditions builds up a set rather than overwriting the previous one.
Write reactions without balancing protons
Transformed ("primed") quantities absorb the proton contribution into the pH, so
an explicit H+ participant would count it twice. Write ATP hydrolysis as
ATP + H2O = ADP + phosphate, with no proton. The balance check ignores hydrogen
accordingly.
An INCHIKEY: participant is matched ignoring the final block of the key, which
encodes protonation state — eQuilibrator holds one compound per pseudoisomer
group, so CHEBI:30616 and INCHIKEY:ZKHQWZAMYRWXGA-KQYNXXCUSA-J give identical
results.
See Reaction Thermodynamics for the reasoning behind this, the quantities available, and the cases where the enricher declines to produce a number.
chemrof saturate
Builds a closed, ChEBI-like graph of entities from a file of seed structures. Every seed, and every structure generated from it, is run through a set of generators, and whatever they produce is fed back in until nothing new appears. Entities reached by several routes are merged by id, so the output is one graph with each structure once.
chemrof saturate seeds.tsv -o graph.yaml
chemrof saturate seeds.tsv -f owl -o graph.owl -w 4
echo "OC(=O)CC(O)(CC(O)=O)C(O)=O" | chemrof saturate - -g protonation
The seed file has one structure (SMILES or InChI) per line, optionally
followed by a tab and a name and a tab and an id to use instead of the
InChIKey (e.g. a CHEBI id). Blank lines and lines starting with # are
skipped.
N[C@@H](C)C(O)=O L-alanine CHEBI:16977
OC(=O)CC(O)(CC(O)=O)C(O)=O citric acid CHEBI:30769
| Generator | Produces | Links |
|---|---|---|
stereo |
stereo-agnostic parent, stereoisomers, racemates (as --siblings); for an atom, its element's family |
enantiomer_form_of, has_left_enantiomer, ... |
salt |
a salt's cation and anion | has_cationic_component, has_anionic_component |
protonation |
uncharged parent and major microspecies at pH 7.3 | has_major_microspecies_at_pH7_3; also conjugate_acid_of/conjugate_base_of one proton apart, tautomer_of for a zwitterion |
tautomer |
RDKit tautomers (off by default) | tautomer_of |
--generators picks a subset (default stereo,salt,protonation). L-alanine,
for example, saturates to alanine, L-, D- and rac-alanine, and the zwitterion
of each: 8 entities.
The pH 7.3 forms come from a rule-based predictor (chemrof.converter.protonation)
written to follow Rhea's chebi_pH7_3_mapping.tsv. Only the major microspecies
is generated, not the intermediate charge states, so conjugate_acid_of /
conjugate_base_of are asserted only when the two forms are one proton apart.
Ids. Entities are keyed by InChIKey. Standard InChI drops the charge
separation of a zwitterion, so a zwitterion is keyed
chemrof:zwitterion-<InChIKey> to keep it apart from its uncharged form, as
ChEBI does. A seed's own id (third column) replaces its InChIKey everywhere
once saturation is done.
Names. Seeds keep their names. A generated entity whose name is still its
formula is named after a named relative: L-alanine zwitterion,
rac-lactic acid, (R)-lactic acid, citric acid(3-).
Large stereo families. A molecule with more than --max-siblings
stereoisomers (default 64) gets only its stereo-agnostic parent, its mirror
image and their racemate, not a sample of the family.
Limits. --max-rounds stops a given number of generations from the
seeds; --max-entities stops expanding past a size. --workers N splits the
seeds into chunks saturated in parallel and merged; --max-entities then
applies per chunk. The run summary goes to stderr. --enrichers works as for
convert and runs on the whole graph.
chemrof from-smiles (deprecated)
Hidden alias for chemrof convert. Still works for backwards compatibility
but does not support --classes or --autochain.
Examples
# Ethanol as YAML
chemrof convert CCO
# InChI input
chemrof convert "InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3"
# Benzene and water as JSON
chemrof convert c1ccccc1 O --format json
# Calcium ion with PubChem enrichment
chemrof convert "[Ca+2]" --enrichers pubchem
# Build a ChemOnt lookup store from Zenodo, then classify ethanol
chemrof prepare-chemont-from-zenodo chemont.duckdb
chemrof convert CCO --enrichers chemont --chemont-source chemont.duckdb
# Emit ChemOnt classifications as OWL SubClassOf axioms
chemrof convert CCO --enrichers chemont --chemont-source chemont.duckdb --format owl
# Enantiomer detection
chemrof convert "C[C@@H](N)C(=O)O" --format json
# Full racemic mixture graph
chemrof convert "CC(N)C(=O)O" --classes RacemicMixture --format json
# Multiple ions as OWL
chemrof convert "[Ca+2]" "[Cl-]" "[NH4+]" --format owl
# Pipe OWL output to a file
chemrof convert CCO --format owl > ethanol.ofn