orthonym.validation#
Note
Internal API. Names and behaviour may change between releases.
Orthonym Validation Module
Tools for validating generated IUPAC names against external resolvers (OPSIN, PubChem) and measuring name quality (atom coverage).
- orthonym.validation.lookup_name_pubchem(name, cache, skip_api=False)#
Look up an IUPAC name via PubChem PUG-REST API with cache.
Cache-first: if the name is in cache, the cached value is returned immediately (even if it is None, indicating a previous negative result). Only makes an API call on cache miss.
- Parameters:
name (str) – IUPAC name to look up.
cache (dict) – Mutable cache dictionary (updated in-place on API call).
skip_api (bool) – If True, return None on cache miss without calling API. Useful for offline/test mode.
- Returns:
Dict with ‘smiles’ and ‘inchi’ keys on success, or None if the name cannot be resolved. Cached negative results also return None.
- Return type:
Dict[str, str] | None
- orthonym.validation.load_cache(cache_path=None)#
Load PubChem lookup cache from a JSON file.
- Parameters:
cache_path (Path | None) – Path to cache file. Defaults to data/pubchem_cache.json.
- Returns:
Dictionary mapping IUPAC names to lookup results (or None for cached negative results). Returns empty dict if file is missing or corrupt.
- Return type:
dict
- orthonym.validation.save_cache(cache, cache_path=None)#
Save PubChem lookup cache to a JSON file.
- Parameters:
cache (dict) – Dictionary mapping names to results.
cache_path (Path | None) – Path to cache file. Defaults to data/pubchem_cache.json.
- class orthonym.validation.DualResult(opsin_parsed=False, opsin_rt=False, pubchem_resolved=False, pubchem_rt=False, combined_rt=False, opsin_smiles=None, pubchem_inchi=None)#
Bases:
objectResult of dual OPSIN+PubChem validation for a single compound.
- opsin_parsed: bool = False#
- opsin_rt: bool = False#
- pubchem_resolved: bool = False#
- pubchem_rt: bool = False#
- combined_rt: bool = False#
- opsin_smiles: str | None = None#
- pubchem_inchi: str | None = None#
- orthonym.validation.validate_compound(smiles, name, opsin_jar=None, pubchem_cache=None, skip_pubchem_api=False)#
Validate a generated IUPAC name using OPSIN (primary) and PubChem (fallback).
Strategy: 1. Compute InChI of the original SMILES. 2. Try OPSIN parse-back: parse name -> compute InChI -> compare. 3. If OPSIN fails to parse, try PubChem lookup: get InChI -> compare. 4. combined_rt = opsin_rt OR pubchem_rt.
- Parameters:
smiles (str) – Original SMILES string.
name (str) – Generated IUPAC name to validate.
opsin_jar (str | None) – Path to OPSIN JAR file (auto-detected if None).
pubchem_cache (dict | None) – Mutable dict used as PubChem lookup cache.
skip_pubchem_api (bool) – If True, use only cached PubChem results.
- Returns:
DualResult with all validation fields populated.
- Return type:
- orthonym.validation.validate_name_format(name)#
Validate an IUPAC name for known OPSIN-incompatible patterns.
This is a diagnostic heuristic validator . It checks for patterns known to cause OPSIN parse failures. It is NOT a full OPSIN parser reimplementation.
- Parameters:
name (str) – Generated IUPAC name string.
- Returns:
Tuple of (is_valid, reason). If valid, returns (True, “ok”). If invalid, returns (False, “reason_code: details”).
- Return type:
Tuple[bool, str]
- Checks performed:
Empty name
Unbalanced brackets (parentheses, square brackets, braces)
Empty parentheses
Bare ‘oxy’ prefix (not part of a larger word)
Double hyphens – (except within VB descriptors)
Multi-word name validation against OPSIN word rules
Bracket nesting hierarchy verification
- orthonym.validation.opsin_parse(name, jar_version='2.9.0')#
Parse an IUPAC name with OPSIN CLI JAR.
- Parameters:
name (str) – IUPAC name to parse.
jar_version (str) – OPSIN version to use (default “2.9.0”).
- Returns:
SMILES string if OPSIN successfully parsed the name, or None if parsing failed.
- Return type:
str | None
- orthonym.validation.opsin_roundtrip_check(smiles, name, jar_version='2.9.0')#
Full round-trip validation: name -> OPSIN -> SMILES -> InChI comparison.
- Parameters:
smiles (str) – Original SMILES string.
name (str) – Generated IUPAC name to validate.
jar_version (str) – OPSIN version to use.
- Returns:
Dict with keys – - passed: bool – True if InChI matches - opsin_smiles: str or None – SMILES from OPSIN - inchi_match: bool – whether InChI strings match - error: str or None – error description if failed
- Return type:
dict
- class orthonym.validation.OpsinGrammar(stats=None)#
Bases:
objectOPSIN-grammar pre-validator with bounded round-trip-gated repair.
- Three responsibilities (internal notes <domain>):
validate(name) -> bool — fast OPSIN-XML-driven check across three surfaces (bracket nesting, stereo position, hyphen placement). Layered ON TOP OF a phase’s format_validator.validate_name_format heuristic pre-screen per.
suggest_fix(name, source_smiles=None) — bounded repair function with the LOCKED signature (name FIRST). Each repair candidate is gated through validate AND opsin_roundtrip_check(smiles, candidate) (SMILES-first inside the oracle). One attempt per repair class; NO retry loop (/).
Per-instance telemetry via self._stats and get_validation_stats.
Construction: OpsinGrammar (or OpsinGrammar(stats=shared_dict) to share counters with an outer container per ref-pass pattern).
- STAT_KEYS: Tuple[str, ...] = ('validate_passed', 'repair_succeeded_bracket', 'repair_succeeded_stereo', 'repair_succeeded_hyphen', 'repair_failed_validate', 'repair_failed_roundtrip', 'no_repair_offered')#
- validate(name)#
Fast OPSIN-grammar pre-validation. Returns True on pass.
- suggest_fix(name, source_smiles=None)#
Bounded round-trip-gated repair (LOCKED signature).
- Parameters:
name (str) – The (validate-failing) IUPAC name to repair. FIRST positional arg per.
source_smiles (str | None) – Original SMILES for the round-trip gate. When None the round-trip gate is replaced by a validate-only check and an INFO log records the degraded path .
- Returns:
(repaired_name, repair_class) if a repair fired AND re-validates AND (when source_smiles is given) round- trips through OPSIN. Otherwise (None, None).
- Return type:
Tuple[str | None, str | None]
Notes
Tries each repair class once in deterministic order [bracket, hyphen, stereo]. NO retry loop (/).
The round-trip oracle is called as opsin_roundtrip_check(source_smiles, candidate) — SMILES-first per. The (smiles, name) arg order to the ORACLE is the OPPOSITE of suggest_fix’s own signature; this is the most common confusion source in the codebase and the grep gate enforces it.
- get_validation_stats()#
Return a defensive copy of the per-instance counters .
- last_repair_class()#
Diagnostic accessor: most recent successful repair class.
- orthonym.validation.opsin_grammar_validate(name)#
Module-level convenience wrapper around OpsinGrammar.validate.
Backed by an internal singleton with its own _stats counter (NOT shared with any Orthonym instance per).
- orthonym.validation.opsin_grammar_suggest_fix(name, source_smiles=None)#
Module-level convenience wrapper around OpsinGrammar.suggest_fix.
Return-shape divergence: OpsinGrammar.suggest_fix returns Tuple[Optional[str], Optional[str]] (per LOCKED); this helper drops the repair-class slot and returns Optional[str] only, for backward-compatible callers that just want the repaired name. Use the class API directly when the repair class is needed.
- class orthonym.validation.CoverageResult(total_heavy_atoms, claimed_atoms, unclaimed_atoms, coverage_ratio, is_complete, method, claimed_atom_indices=<factory>, unclaimed_atom_indices=<factory>, extra_atoms=0, parsed_heavy_atoms=0, constitution_match=False, input_inchikey='', parsed_inchikey='', parsed_smiles='')#
Bases:
objectResult of atom coverage validation.
- Variables:
total_heavy_atoms (int) – Heavy atom count of the input molecule.
claimed_atoms (int) – Input heavy atoms with a counterpart in the parse-back, counted as the size of the element-multiset intersection.
unclaimed_atoms (int) – Input heavy atoms with NO counterpart (atoms the name dropped) = total_heavy_atoms - claimed_atoms.
coverage_ratio (float) – claimed / total (0.0 to 1.0). Diagnostic only – it is NOT what decides
is_complete, and by construction it cannot see atom gain (seeextra_atoms) or a rearrangement (seeconstitution_match).is_complete (bool) – True only when the parse-back has the SAME CONSTITUTION as the input, i.e.
constitution_match. Never a threshold on a count.method (str) – ‘parse_back’, ‘trivial’, ‘parse_back_no_inchi’, or ‘unavailable’.
claimed_atom_indices (Set[int]) – Indices of atoms accounted for. Empty on the parse-back path, which compares whole structures rather than mapping atom to atom.
unclaimed_atom_indices (Set[int]) – Indices of atoms NOT accounted for. Empty on the parse-back path, as above.
extra_atoms (int) – Heavy atoms present in the parse-back that the INPUT does not have – atoms the name invented. Zero for a faithful name.
parsed_heavy_atoms (int) – Raw heavy-atom count of the parse-back, unclamped, so a caller can recover the true count ratio (which may exceed 1).
constitution_match (bool) – InChIKey skeleton block of input == that of the parse-back. Fixes formula, connectivity and H layer; does not fix stereo, isotopes or charge (module docstring, DECLARED SCOPE).
input_inchikey (str) – Full InChIKey of the input molecule (’’ if unavailable).
parsed_inchikey (str) – Full InChIKey of the parse-back (’’ if unavailable).
parsed_smiles (str) – OPSIN’s SMILES of the parse-back (’’ if unavailable).
- total_heavy_atoms: int#
- claimed_atoms: int#
- unclaimed_atoms: int#
- coverage_ratio: float#
- is_complete: bool#
- method: str#
- claimed_atom_indices: Set[int]#
- unclaimed_atom_indices: Set[int]#
- extra_atoms: int = 0#
- parsed_heavy_atoms: int = 0#
- constitution_match: bool = False#
- input_inchikey: str = ''#
- parsed_inchikey: str = ''#
- parsed_smiles: str = ''#
- orthonym.validation.validate_atom_coverage(mol, name, opsin_jar=None)#
Validate how completely name describes mol.
Parses name back to a molecule via OPSIN and compares that molecule to mol by CONSTITUTION (InChIKey skeleton block). Heavy-atom counts are reported as diagnostics – dropped atoms as
unclaimed_atoms, invented atoms asextra_atoms– but never decideis_complete, because a count cannot distinguish the same atoms from the same number of atoms.- Parameters:
mol – RDKit Mol object for the input molecule.
name (str) – Generated IUPAC name to validate.
opsin_jar (str | None) – Path to OPSIN JAR. If
None, attempts auto-discovery. If OPSIN is unavailable, returns a result with method=’unavailable’.
- Returns:
A:class:CoverageResult with coverage metrics.
- Return type: