OPSIN: systematic names to structures¶
OPSIN (Open Parser for Systematic IUPAC Nomenclature, University of Cambridge)
reads a systematic chemical name and returns its structure. It parses the
name, and does not look it up. 2-acetyloxybenzoic acid works because the
name describes the molecule. aspirin does not, because nothing in the word
says what the structure is. For names like that, use Search("name"), which
looks names up in the offline databases.
There are two ways to run it:
OPSINcalls the web service hosted by EMBL-EBI. It needs the network, but no installation.PYOPSINruns the same parser locally throughpy2opsin, which bundles OPSIN's jar. It needs a Java runtime, but no network, and it is the one to use for more than a handful of names.
The outputs below were produced on 2026-09-23.
import pandas as pd
from provesid import OPSIN, PYOPSIN
pd.set_option("display.width", 200)
pd.set_option("display.max_colwidth", 60)
opsin = OPSIN()
One name¶
opsin.get_id("1,3,7-trimethylpurine-2,6-dione")
{'iupac_name': '1,3,7-trimethylpurine-2,6-dione',
'status': 'SUCCESS',
'message': '',
'inchi': 'InChI=1/C8H10N4O2/c1-10-4-9-6-5(10)7(13)12(3)8(14)11(6)2/h4H,1-3H3',
'stdinchi': 'InChI=1S/C8H10N4O2/c1-10-4-9-6-5(10)7(13)12(3)8(14)11(6)2/h4H,1-3H3',
'stdinchikey': 'RYYVLZVUVIJVGH-UHFFFAOYSA-N',
'smiles': 'CN1C(N(C=2N=CN(C2C1=O)C)C)=O'}
status is OPSIN's own, SUCCESS or FAILURE. inchi is OPSIN's
non-standard InChI, and stdinchi and stdinchikey are the standard ones to
compare with other sources.
What OPSIN can and cannot read¶
A name that does not describe a structure fails, and message says which part
of the name OPSIN could not read:
names = ["propan-2-ol", "isopropanol", "propan-2-one", "acetone",
"2-acetyloxybenzoic acid", "aspirin", "table salt", ""]
rows = []
for name in names:
result = opsin.get_id(name)
rows.append({"name": name, "status": result["status"], "smiles": result["smiles"],
"message": result["message"][:70]})
pd.DataFrame(rows)
| name | status | smiles | message | |
|---|---|---|---|---|
| 0 | propan-2-ol | SUCCESS | CC(C)O | |
| 1 | isopropanol | SUCCESS | C(C)(C)O | |
| 2 | propan-2-one | SUCCESS | CC(C)=O | |
| 3 | acetone | SUCCESS | CC(=O)C | |
| 4 | 2-acetyloxybenzoic acid | SUCCESS | C(C)(=O)OC1=C(C(=O)O)C=CC=C1 | |
| 5 | aspirin | FAILURE | aspirin was uninterpretable due to the following section... | |
| 6 | table salt | FAILURE | table salt was uninterpretable due to the following sect... | |
| 7 | FAILURE | empty name |
OPSIN also knows some traditional names that are unambiguous ("acetone", "isopropanol"), but not trade or trivial names such as "aspirin". A blank name fails without a request being sent.
Many names¶
get_id_from_list sends one request per name, paced for the service:
iupac_names = ["benzene", "methylbenzene", "phenol", "aniline", "benzoic acid",
"N-(4-hydroxyphenyl)acetamide", "2-[4-(2-methylpropyl)phenyl]propanoic acid"]
results = opsin.get_id_from_list(iupac_names, pause_time=0.2)
pd.DataFrame(results)[["iupac_name", "status", "smiles", "stdinchikey"]]
| iupac_name | status | smiles | stdinchikey | |
|---|---|---|---|---|
| 0 | benzene | SUCCESS | C1=CC=CC=C1 | UHOVQNZJYSORNB-UHFFFAOYSA-N |
| 1 | methylbenzene | SUCCESS | CC1=CC=CC=C1 | YXFVVABEGXRONW-UHFFFAOYSA-N |
| 2 | phenol | SUCCESS | C1(=CC=CC=C1)O | ISWSIDIOOBJBQZ-UHFFFAOYSA-N |
| 3 | aniline | SUCCESS | NC1=CC=CC=C1 | PAYRUJLWNCNPSJ-UHFFFAOYSA-N |
| 4 | benzoic acid | SUCCESS | C(C1=CC=CC=C1)(=O)O | WPYMKLBDIGXBTP-UHFFFAOYSA-N |
| 5 | N-(4-hydroxyphenyl)acetamide | SUCCESS | OC1=CC=C(C=C1)NC(C)=O | RZVAJINKPMORJF-UHFFFAOYSA-N |
| 6 | 2-[4-(2-methylpropyl)phenyl]propanoic acid | SUCCESS | CC(CC1=CC=C(C=C1)C(C(=O)O)C)C | HEFNNWSXXWATRW-UHFFFAOYSA-N |
pause_time is an extra sleep after each name. Underneath it, the shared HTTP
transport keeps requests to the same host at least 0.1 s apart and retries a
momentary failure with back-off.
Offline, with PYOPSIN¶
PYOPSIN returns records of the same shape, and needs no network. The first
call starts a Java process, which takes a few seconds, so it pays off on a
list:
local = PYOPSIN()
local.get_std_inchikey("2-[4-(2-methylpropyl)phenyl]propanoic acid")
'HEFNNWSXXWATRW-UHFFFAOYSA-N'
local_results = local.get_id_from_list(iupac_names)
pd.DataFrame(local_results)[["iupac_name", "status", "smiles", "stdinchikey"]]
| iupac_name | status | smiles | stdinchikey | |
|---|---|---|---|---|
| 0 | benzene | SUCCESS | C1=CC=CC=C1 | UHOVQNZJYSORNB-UHFFFAOYSA-N |
| 1 | methylbenzene | SUCCESS | CC1=CC=CC=C1 | YXFVVABEGXRONW-UHFFFAOYSA-N |
| 2 | phenol | SUCCESS | C1(=CC=CC=C1)O | ISWSIDIOOBJBQZ-UHFFFAOYSA-N |
| 3 | aniline | SUCCESS | NC1=CC=CC=C1 | PAYRUJLWNCNPSJ-UHFFFAOYSA-N |
| 4 | benzoic acid | SUCCESS | C(C1=CC=CC=C1)(=O)O | WPYMKLBDIGXBTP-UHFFFAOYSA-N |
| 5 | N-(4-hydroxyphenyl)acetamide | SUCCESS | OC1=CC=C(C=C1)NC(C)=O | RZVAJINKPMORJF-UHFFFAOYSA-N |
| 6 | 2-[4-(2-methylpropyl)phenyl]propanoic acid | SUCCESS | CC(CC1=CC=C(C=C1)C(C(=O)O)C)C | HEFNNWSXXWATRW-UHFFFAOYSA-N |
The InChIKeys agree with the web service's:
[a["stdinchikey"] == b["stdinchikey"] for a, b in zip(results, local_results)]
[True, True, True, True, True, True, True]
Failures, caching and Search¶
Nothing raises. A service that cannot be reached comes back as FAILURE with
the reason in message. A successful parse is cached on disk, and a failure
is not, so a name that failed because the network was down is asked again
next time.
Search("name", use_opsin=True) adds OPSIN's parse of every name query as
one more candidate, in the opsin_smiles column. See
Resolving identifiers with Search.