Aller au contenu

omnilab/cactus

Omnilab Cactus: the NCI/CADD Chemical Identifier Resolver - a structure from a name, a CAS number, an InChIKey, an InChI or a SMILES, and its standard InChIKey, InChI, SMILES, IUPAC name, formula, molecular weight, CAS numbers, names and picture; OPSIN for a name it cannot read; no key.

Étage
Fournisseur · Omnilab
Version
1.x
Suite de conformité
non branchée
Vérifié en réel
Son README ne le dit pas
Licence
MIT
composer require omnilab/cactus:1.x-dev

The NCI/CADD Chemical Identifier Resolver (CIR: https://cactus.nci.nih.gov/chemical/structure/) for omnisense/omnilab: a structure from a name, a CAS number, an InChIKey, an InChI or a SMILES - its standard InChIKey (its id), InChI, SMILES, IUPAC name, formula, weight, CAS numbers, names and picture -, as an entity; OPSIN (the EBI's IUPAC name parser) for a name CIR cannot read. No key.

Installation

composer require omnilab/cactus
use Omnilab\Cactus\CactusSourceFactory;

$cactus = (new CactusSourceFactory($httpClient))->create([
    'mailto' => 'support@glitchr.io',  // optional
    'representations' => ['stdinchi', 'smiles', 'iupac_name', 'formula', 'mw', 'cas', 'names'],  // optional: one call each
    'names' => 20,                     // optional: synonyms kept; 0: the names are not asked
    'opsin' => true,                   // optional: false, or another OPSIN's address (https://.../opsin/ws/)
]);

Credentials

None: CIR and OPSIN's web service take no key and no account.

Options

The factory's options, with the defaults it applies (CactusSourceFactory):

Option Type Default Required Effect
representations list<string> [stdinchi, smiles, iupac_name, formula, mw, cas, names] no what entity() asks after the InChIKey, one call each, of stdinchi, smiles, iupac_name, formula, mw, cas, names (another is an \InvalidArgumentException at create())
names int 20 no how many of CIR's names to keep as synonyms; 0: the names are not asked
opsin bool|string true no a name CIR cannot read is given to OPSIN: true (the EBI's, https://www.ebi.ac.uk/opsin/ws/), false (never), or another OPSIN's address
mailto ?string null no a contact address, sent in the User-Agent
user_agent ?string null no the whole User-Agent, in place of the one made from mailto
throttle float 0 no seconds left between two calls: CIR publishes no rate
base_uri string https://cactus.nci.nih.gov/chemical/structure/ no the API the source calls

Capabilities

capabilities() answers entity, structure, cross_references; an operation it does not answer throws NotSupportedException before any call.

Capability Supported
entity yes a name, a CAS number, an InChIKey, an InChI or a SMILES (an Identifier of inchikey, inchi, cas, or a string); another base's identifier refused before any call; one call for the InChIKey, then one per representation (eight by default)
search no CIR resolves one structure; it does not search
series no
products no
occurrences no
datasets no
jobs no
open no
structure yes the image is CIR's picture of the structure, by its InChI
cross_references yes the standard InChIKey (its id), the standard InChI and the CAS numbers
forecast no
historical no
area no
station no
cloud_cover no

What it reads

entity() takes an Identifier of the inchikey, inchi or cas scheme, or a string: one of those, or a name, or a SMILES (what no scheme reads). An identifier of another base (pubchem.cid, chebi, wikidata...) is refused before any call: CIR does not resolve them. The entity's id is the standard InChIKey (CIR keeps no id of its own); identifiers hold the standard InChI and the CAS numbers CIR gives (CAS's deleted and merged numbers too: CIR lists them, each checked).

The calls

Calls
first {structure}/stdinchikey/xml: the InChIKey, and what CIR read the string as (string_class: "IUPAC name (OPSIN)", "SMILES string", "chemical name (CIR)", "Standard InChIKey"...). CIR may read a string several ways (CCO: a SMILES, ethanol; and a name of another compound); the text answers give the first reading, as this one keeps
then one call each for stdinchi, smiles, iupac_name, formula, mw, cas, names (the representations option)
a name CIR cannot read www.ebi.ac.uk/opsin/ws/{name}.json (OPSIN): its standard InChI, InChIKey and SMILES (properties of source opsin); then CIR by that InChI for the rest (CIR computes a formula and a weight for any structure; a name or CAS number only when its tables hold the structure)

The structure is written in the path as CIR wants it: encoded, but its slashes left as they are (CIR's server answers 404 to %2F: an InChI or a SMILES with / would be unreadable). Unknown is an XML answer without data (and OPSIN's 404 or FAILURE); a text representation CIR does not have is a 404, or a 500 whose page says "Page not found" (CIR's answer for a name it cannot read, in text): null either way. Another 5xx is an UnavailableException.

What an entity holds

label: the name asked, when CIR (or OPSIN) read the string as a name; else CIR's first name, its IUPAC name, the InChIKey. synonyms: CIR's names (names of them), the CAS numbers left out. image: CIR's picture of the structure, by its InChI. domain chemistry, kind compound.

Key Representation Value
read_as the XML's string_class what CIR read the string as; IUPAC name (OPSIN web service) when OPSIN read it
molecular_formula formula C8H10N4O2
molecular_weight mw g/mol (194.1926)
iupac_name iupac_name text
smiles smiles CIR's SMILES (and OPSIN's, source opsin)
inchi, inchikey stdinchi, stdinchikey standard (and OPSIN's, source opsin)

The Collector

The InChIKey, the InChI and the CAS numbers are identifiers other sources read: with PubChem (or ChEBI, ChEMBL) configured, $collector->collect('cactus', 'caffeine', 1, ['pubchem']) follows the InChIKey into PubChem - walked on 2026-10-15 (the workspace's .samples/live.php, not published). CIR is the entry point for what has no id yet: a name, a SMILES, a drawn structure's InChI.

Example

<?php

require __DIR__.'/vendor/autoload.php';

use Omnilab\Cactus\CactusSourceFactory;
use Omnilab\Model\Scheme;
use Symfony\Component\HttpClient\HttpClient;

$cactus = (new CactusSourceFactory(HttpClient::create()))->create(['mailto' => 'you@example.org', 'names' => 3]);

$caffeine = $cactus->entity('caffeine');
echo $caffeine->label, ' (', $caffeine->id, '), read as ', $caffeine->value('read_as'), ': ', $caffeine->value('molecular_formula'), ', ', $caffeine->property('molecular_weight'), "\n";
echo '  CAS ', implode(', ', $caffeine->identifiers()->values(Scheme::CAS)), '; names: ', implode(', ', $caffeine->synonyms), "\n";

$butene = $cactus->entity('C/C=C/C');   // a SMILES, its slashes kept raw in the path
echo $butene->label, ' (', $butene->id, '), read as ', $butene->value('read_as'), "\n";
var_dump($cactus->entity('zzqqxx'));
caffeine (inchikey:RYYVLZVUVIJVGH-UHFFFAOYSA-N), read as IUPAC name (OPSIN): C8H10N4O2, 194.1926 g/mol
  CAS 71701-02-5, 95789-13-2, 58-08-2; names: 1,3,7-trimethylpurine-2,6-dione, 1,3,7-Trimethylxanthine, SDCCGMLS-0064595.P002
(E)-But-2-ene (inchikey:IAQRGUVFOMOMEM-ONEGZZNKSA-N), read as SMILES string
NULL

(as answered on 2026-10-27; CIR lists caffeine's deleted and merged CAS numbers beside 58-08-2, each a valid CAS number; "zzqqxx" is an empty XML answer, then OPSIN's 404: null)

# config/packages/omnilab.yaml
omnilab:
    sources:
        cactus: { factory: cactus, options: { mailto: '%env(OMNILAB_MAILTO)%', names: 20, opsin: true } }

Errors

CIR's calls are made by the source itself (the structure's slashes must stay raw in the path), and mapped as HttpSource maps them: a 404, or a 500 whose page says "Page not found" (CIR's answer for a name it cannot read in text), is null; a 429 a RateLimitedException (its retryAfter not read); another 5xx, a time-out or a host that does not answer an UnavailableException; any other 4xx a ProviderException with its status (CIR's body is not read into the message). The throttle applies to these calls too. OPSIN's 404 or FAILURE is null.

Before any call: an identifier of a base CIR does not resolve (pubchem.cid, chebi, wikidata...) (NotSupportedException).

Verified, and not

Against the real services on 2026-10-15: caffeine by its name (read by CIR's OPSIN), by its CAS number (read as a name by CIR's tables) and by InChIKey; "CCO" (read as a SMILES: ethanol, though CIR also reads it as another compound's name); aspirin by its InChIKey; an InChI whose structure CIR holds no name for (its formula and weight computed; no IUPAC name, CAS or names: 404s); "zzqqxx" (an empty XML answer; OPSIN's 404); OPSIN's answer for "2-methyl-3-(2-methylcyclopropyl)pentanedioic acid"; a SMILES with slashes (C/C=C/C, raw); %2F refused by CIR (404); "zzqqxx" in text answered by a 500 "Page not found". The fixtures in Tests/Fixtures/ are those answers. A call took 0.5 to 1.4 s; an entity eight calls, about 6 s.

Again on 2026-10-27: caffeine by its name (read by CIR's OPSIN; three CAS numbers, three names), C/C=C/C as a SMILES, "zzqqxx" null (the example above).

Not verified: OPSIN as a fallback against a name CIR really failed to read - every systematic name tried that day was read by CIR's own OPSIN (the test makes CIR fail on one to follow the path, with both services' real answers); a rate limit (none published, none met); CIR's other representations (SDF, other identifiers), not asked.

Responsibility

This package is an independent, unofficial client: it is not affiliated with, endorsed by or sponsored by the NCI/CADD Group (National Cancer Institute), the University of Cambridge or the EMBL-EBI. CIR publishes no rate nor terms beyond its disclaimer; OPSIN is open source (MIT) and to be cited (Lowe, D. M. et al., J. Chem. Inf. Model. 51, 739-753, 2011), as the README's Terms of use says. Whoever runs the package is responsible for any use made of what it returns: see the README's Responsibility section.

Proposer une modification docs/index.md