Alkahest
Find out which solvents a written experimental procedure actually used, and what role each one played.
Alkahest reads free-text procedures of the kind found in patents and papers, and returns the solvents as SMILES, sorted into the stage of the experiment they belong to. It is a rule-based pipeline: a dictionary of name variants, phase boundary detection, and local context rules. There is no model to train and no network call, so the same text always gives the same answer.
This package accompanies the paper The Stubborn Persistence of Toxic Solvents in Chemical Synthesis by Maarten R. Dobbelaere and Helen F. Sneddon, accepted in Angewandte Chemie International Edition.
An interactive companion to the study, which lets you look up the solvents actually used for a given reaction type across 1.34M USPTO reactions (1976-2016), is available at solventexplorer.com.
Installation
pip install alkahest-chem
Note on the name. The distribution is
alkahest-chem; the import name isalkahest. Do not runpip install alkahest— that name belongs to an unrelated computer algebra system on PyPI.
Optional extras:
| Extra | Adds | Install |
|---|---|---|
rdkit |
SMILES canonicalisation, so GSK scoring accepts any valid notation | pip install "alkahest-chem[rdkit]" |
ord |
Reading Open Reaction Database protocol buffer files | pip install "alkahest-chem[ord]" |
rxn-insight |
Reaction classification via Rxn-INSIGHT | pip install "alkahest-chem[rxn-insight]" |
all |
All of the above | pip install "alkahest-chem[all]" |
Quick Start
from alkahest import extract_solvents_categorized
result = extract_solvents_categorized("""
The compound (1.0 g) was dissolved in THF (20 mL) and stirred for 2 h.
The mixture was quenched with water and extracted with ethyl acetate.
The combined organic layers were washed with brine and concentrated.
Purification by column chromatography (silica gel, hexanes/EtOAc 4:1)
gave the product as a white solid (0.85 g, 78%), mp 112-114 C.
1H NMR (400 MHz, CDCl3): delta 7.45 (d, 2H).
""")
print(result)
# CategorizedSolvents(
# reaction=['thf'],
# workup=['ethyl acetate', 'water'],
# purification=['hexanes', 'etoac'],
# analytical=['cdcl3']
# )
print(result.to_smiles_dict())
# {'reaction': 'C1CCOC1',
# 'workup': 'CCOC(C)=O.O',
# 'purification': 'CCCCCC.CCOC(C)=O',
# 'analytical': '[2H]C(Cl)(Cl)Cl'}
Every solvent is placed in exactly one of four roles:
| Role | Meaning | Typical cue in the text |
|---|---|---|
reaction |
the reaction medium itself | "dissolved in", "a solution of", anything before the first phase boundary |
workup |
post-reaction processing | "quenched with", "extracted with", "washed with" |
purification |
chromatography, recrystallisation | "column", "silica", "eluted with" |
analytical |
NMR, MS and other measurements | after an "NMR" marker; deuterated solvents always land here |
A category with no solvents reads "solvent-free" in the SMILES dict, and
several solvents in one category are joined with . into a single SMILES string.
Scoring solvents
score_solvent grades a solvent with the 2016 GSK Solvent Selection Guide.
Mixtures take the worst component's score, and deuterated solvents are scored as
their non-deuterated parent.
from alkahest import score_solvent
score_solvent("C1CCOC1") # THF -> 'R' (red: major issues)
score_solvent("CS(C)=O") # DMSO -> 'A' (amber: some issues)
score_solvent("CCOC(C)=O") # EtOAc -> 'G' (green: few issues)
score_solvent("CCOC(C)=O.C1CCOC1") # worst of the two -> 'R'
score_solvent("[2H]C(Cl)(Cl)Cl") # CDCl3, scored as chloroform -> 'R'
score_solvent("CCN(C(C)C)C(C)C") # DIPEA, not in the guide -> 'Unknown'
NMR solvents
Patent text spells NMR solvents in hundreds of ways, including OCR damage.
map_nmr_solvent resolves them to a canonical name and SMILES, and returns
None for anything it does not recognise.
from alkahest import map_nmr_solvent
map_nmr_solvent("CDCl3") # ('CDCl3', '[2H]C(Cl)(Cl)Cl')
map_nmr_solvent("cdc13") # OCR damage, digit 1 for letter l -> same result
map_nmr_solvent("dmso-d6") # ('DMSO-d6', '[2H]C([2H])([2H])S(=O)C([2H])([2H])[2H]')
map_nmr_solvent("meod") # ('CD3OD', '[2H]C([2H])([2H])O[2H]')
map_nmr_solvent("xyzzy") # None
Whole DataFrames
extract_solvents_batch adds four columns to a DataFrame of procedures. It
modifies the frame in place and returns it, so pass a copy if you need the
original untouched.
import pandas as pd
from alkahest import extract_solvents_batch
df = pd.read_parquet("reactions.parquet")
df = extract_solvents_batch(df, procedure_column="procedure")
# Adds SOLV_RXN, SOLV_WORKUP, SOLV_PURIF, SOLV_ANAL
Command line
Installing the package provides two commands. Both split their input into chunks, so a large corpus can run as an array job on a cluster.
# Solvent extraction: chunk 0 of 130
alkahest-extract --input reactions.parquet --output_dir output/ \
--job_index 0 --n_chunks 130
# Rxn-INSIGHT reaction analysis, 6 cores
alkahest-rxn-insight --input reactions.parquet --output_dir output/ \
-i 0 -n 1000 -c 6
Chunk i of n covers rows i * len(df) // n to (i + 1) * len(df) // n, so
the chunks tile the input exactly whatever the row count. Under SLURM:
#SBATCH --array=0-129
alkahest-extract --input reactions.parquet --output_dir output/ \
--job_index $SLURM_ARRAY_TASK_ID --n_chunks 130
API
| Function | Returns |
|---|---|
extract_solvents_categorized(text) |
CategorizedSolvents with a dict per role |
extract_solvents_batch(df, ...) |
the DataFrame, with four solvent columns added |
find_phase_boundaries(text) |
character offset where each phase begins, or None |
score_solvent(smiles) |
'G', 'A', 'R' or 'Unknown' |
get_gsk_guide() |
the guide as a DataFrame |
map_nmr_solvent(text) |
(name, smiles) or None |
extract_nmr_snippet(procedure) |
the raw solvent snippet following "NMR" |
curate_nmr_snippet(snippet) |
that snippet with frequencies and shifts stripped |
The dictionaries are importable too: SOLVENT_DICT, DEUTERATED_SOLVENTS and
NMR_SOLVENT_DICTIONARY.
Development
git clone https://github.com/mrodobbe/Alkahest.git
cd Alkahest
pip install -e ".[test]"
pytest --cov=alkahest --cov-report=term-missing
The HTML documentation is built with Sphinx:
pip install ".[docs]"
sphinx-build -b html docs docs/_build/html
License
This software is available for academic and non-commercial use only. See LICENSE for details. For commercial licensing, contact mrodobbe.Dobbelaere@UGent.be.
Citation
If you use this software, please cite:
M. R. Dobbelaere and H. F. Sneddon, "The Stubborn Persistence of Toxic Solvents
in Chemical Synthesis", Angew. Chem. Int. Ed., 2026, accepted.
If you use the GSK solvent scoring functionality (score_solvent, get_gsk_guide), please also cite the original guide:
C.M. Alder, J.D. Hayler, R.K. Henderson, A.M. Redman, L. Shukla, L.E. Shuster, H.F. Sneddon,
"Updating and further expanding GSK's solvent sustainability guide",
Green Chem., 2016, 18, 3879-3890. DOI: 10.1039/C6GC00611F
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file alkahest_chem-0.1.0.tar.gz.
File metadata
- Download URL: alkahest_chem-0.1.0.tar.gz
- Upload date:
- Size: 77.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1378501af247a9fc473ca4b8e0300855c12d195d24cf62a0c7ce0f836957bb5
|
|
| MD5 |
dea12aa7be5cd5b72b3cf908ad036c12
|
|
| BLAKE2b-256 |
82c73145a79840d3337438cd76651f7f6d1ffbe81392ad6336a73d6e850943e5
|
File details
Details for the file alkahest_chem-0.1.0-py3-none-any.whl.
File metadata
- Download URL: alkahest_chem-0.1.0-py3-none-any.whl
- Upload date:
- Size: 65.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55a9ce24d9f44943794d1b54c6da16cffea7063241e3caaa5b589db788a486cc
|
|
| MD5 |
2c4dca9de15cd5219361986c4cda5436
|
|
| BLAKE2b-256 |
d0b1dd1bab46d63a7c7c8aae307a8fe8283270549918898d81d956ced922c13b
|