orthonym.rules.partial_saturation#
Note
Internal API. Names and behaviour may change between releases.
Partial saturation detection and prefix generation for fused ring systems.
This module handles IUPAC 2013 hydro prefixes for partially saturated fused heterocycles and polycyclic compounds: - dihydro- (2 H added) - tetrahydro- (4 H added) - hexahydro- (6 H added) - octahydro- (8 H added) - decahydro- (10 H added) - dodecahydro- (12 H added) - perhydro- (fully saturated, no locants needed)
IUPAC 2013 Blue Book: “Prefixes ‘dihydro’, ‘tetrahydro’, etc. indicate the addition of hydrogen to specified positions of an otherwise unsaturated parent structure.”
Key Rules: 1. Always include locants for partial saturation (2,3-dihydro, not just dihydro) 2. Perhydro means ALL ring atoms saturated - no locants used 3. Saturation prefix comes LAST before parent name, AFTER substituents 4. Order: [substituents]-[saturation prefix]-[indicated H]-[parent]
Reference: IUPAC 2013 Blue Book
- orthonym.rules.partial_saturation.detect_partial_saturation(mol, aromatic_parent_smiles)#
Detect partial saturation by comparing molecule to aromatic parent.
Counts sp3-hybridized atoms in the ring system that would be sp2/aromatic in the parent structure. The IUPAC hydro prefix (dihydro-, tetrahydro-, etc.) indicates the number of hydrogen atoms added, which corresponds to 2 * (number of sp3 ring atoms).
For fused ring systems: - tetrahydroquinoline: 4 positions saturated = 4 sp3 atoms in reduced ring - indoline (2,3-dihydroindole): 2 sp3 atoms at positions 2,3 - decahydronaphthalene (decalin): all 10 ring atoms sp3 = perhydro
- Parameters:
mol (Mol) – RDKit molecule to analyze
aromatic_parent_smiles (str) – SMILES of the fully aromatic parent structure
- Returns:
Dict with saturation info, or None if no saturation detected –
‘sp3_count’: Number of sp3 atoms in ring system
’hydrogen_count’: Number of added hydrogens (sp3_count * 2)
’prefix’: Saturation prefix string (‘dihydro’, ‘tetrahydro’, etc.)
’saturated_indices’: List of atom indices that are sp3
’is_perhydro’: True if fully saturated
- Return type:
Dict[str, Any] | None
Examples
>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCN2') # tetrahydroquinoline >>> result = detect_partial_saturation(mol, 'c1ccc2ncccc2c1') >>> result['prefix'] 'hexahydro' # 3 sp3 carbons * 2 = 6H
- orthonym.rules.partial_saturation.get_saturation_prefix(hydrogen_count)#
Get the IUPAC saturation prefix for a given hydrogen count.
- Parameters:
hydrogen_count (int) – Number of added hydrogens (typically 2, 4, 6, 8, 10, 12)
- Returns:
Saturation prefix string, or None if not a standard count
- Return type:
str | None
Examples
>>> get_saturation_prefix(2) 'dihydro' >>> get_saturation_prefix(4) 'tetrahydro' >>> get_saturation_prefix(10) 'decahydro'
- orthonym.rules.partial_saturation.get_saturation_locants(mol, sp3_atom_indices, atom_to_locant)#
Get IUPAC locants for saturated positions.
Converts atom indices to IUPAC locants using the provided mapping, then sorts them according to IUPAC rules (lowest locant set).
- Parameters:
mol (Mol) – RDKit molecule
sp3_atom_indices (List[int]) – List of atom indices that are sp3 (saturated)
atom_to_locant (Dict[int, int | str]) – Mapping from atom index to IUPAC locant
- Returns:
Sorted list of locants for saturation prefix
- Return type:
List[str | int]
Examples
>>> atom_to_locant = {0: 1, 1: 2, 2: 3, 3: 4,...} >>> get_saturation_locants(mol, [0, 1, 2, 3], atom_to_locant) [1, 2, 3, 4]
- orthonym.rules.partial_saturation.format_saturation_prefix(prefix, locants=None)#
Format saturation prefix with locants for IUPAC name.
IUPAC rules: - Perhydro: no locants (perhydro-) - All others: locants required (2,3-dihydro-, 1,2,3,4-tetrahydro-)
- Parameters:
prefix (str) – Saturation prefix (‘dihydro’, ‘tetrahydro’, ‘perhydro’, etc.)
locants (List[str | int] | None) – Optional list of locants (not used for perhydro)
- Returns:
Formatted prefix string ready for name assembly
- Return type:
str
Examples
>>> format_saturation_prefix('perhydro') 'perhydro' >>> format_saturation_prefix('tetrahydro', [1, 2, 3, 4]) '1,2,3,4-tetrahydro' >>> format_saturation_prefix('dihydro', [2, 3]) '2,3-dihydro'
- orthonym.rules.partial_saturation.get_saturated_position_locants(mol, saturated_indices, atom_to_locant)#
Get IUPAC locants for saturated positions.
This is an alias for get_saturation_locants for clearer naming.
- Parameters:
mol (Mol) – RDKit molecule
saturated_indices (List[int]) – List of atom indices that are saturated
atom_to_locant (Dict[int, int | str]) – Mapping from atom index to IUPAC locant
- Returns:
Sorted list of locants for saturation prefix
- Return type:
List[str | int]
- orthonym.rules.partial_saturation.analyze_saturation_for_naming(mol, aromatic_parent_smiles, atom_to_locant)#
Complete analysis for saturation prefix generation in naming.
This is the main entry point for the composer module. It combines detection, locant assignment, and formatting into a single call.
- Parameters:
mol (Mol) – RDKit molecule to analyze
aromatic_parent_smiles (str) – SMILES of the aromatic parent structure
atom_to_locant (Dict[int, int | str]) – Mapping from atom index to IUPAC locant
- Returns:
Formatted saturation prefix string, or None if no saturation
- Return type:
str | None
Examples
>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCN2') # tetrahydroquinoline >>> parent = 'c1ccc2ncccc2c1' # quinoline >>> atom_to_locant = {...} # IUPAC locant mapping >>> analyze_saturation_for_naming(mol, parent, atom_to_locant) '1,2,3,4-tetrahydro'
- orthonym.rules.partial_saturation.is_fully_saturated(mol, aromatic_parent_smiles)#
Check if a molecule is fully saturated (perhydro) relative to parent.
- Parameters:
mol (Mol) – RDKit molecule to check
aromatic_parent_smiles (str) – SMILES of the aromatic parent
- Returns:
True if the molecule is fully saturated (perhydro)
- Return type:
bool
Examples
>>> mol = Chem.MolFromSmiles('C1CCCC2CCCCC12') # decalin (perhydronaphthalene) >>> is_fully_saturated(mol, 'c1ccc2ccccc2c1') # naphthalene True
- orthonym.rules.partial_saturation.count_ring_sp3_atoms(mol)#
Count sp3-hybridized atoms in ring systems.
Utility function for saturation analysis.
- Parameters:
mol (Mol) – RDKit molecule
- Returns:
Number of sp3 atoms in rings
- Return type:
int
- orthonym.rules.partial_saturation.get_ring_saturation_level(mol, aromatic_parent_smiles)#
Get a human-readable saturation level description.
- Parameters:
mol (Mol) – RDKit molecule
aromatic_parent_smiles (str) – SMILES of aromatic parent
- Returns:
Description string – ‘aromatic’, ‘partially saturated’, or ‘fully saturated’
- Return type:
str
- orthonym.rules.partial_saturation.detect_carbocyclic_partial_saturation(mol, fused_ring_atoms)#
Detect partial saturation in carbocyclic fused systems.
This function identifies partially saturated polycyclic aromatic hydrocarbons like tetrahydronaphthalene, dihydroanthracene, etc. It analyzes the fused ring system to detect if it’s a partially saturated version of a known aromatic parent (naphthalene, anthracene, phenanthrene).
The detection works by: 1. Checking all ring atoms are carbons (pure carbocycle) 2. Counting aromatic vs sp3 atoms 3. Matching the ring system size and structure to known parents 4. Computing the saturation prefix based on sp3 count
IUPAC 2013 Blue Book: - tetrahydronaphthalene: 4 sp3 atoms = tetrahydro prefix - dihydronaphthalene: 2 sp3 atoms = dihydro prefix - decahydronaphthalene (decalin): all sp3 = perhydro or decahydro
- Parameters:
mol (Mol) – RDKit molecule
fused_ring_atoms (Set[int]) – Set of atom indices in the fused ring system
- Returns:
Dict with saturation info, or None if not a recognized partially saturated carbocycle: - ‘parent_name’: Name of aromatic parent (‘naphthalene’, etc.) - ‘parent_smiles’: SMILES of aromatic parent - ‘prefix’: Saturation prefix string (‘tetrahydro’, etc.) - ‘saturated_indices’: List of atom indices that are sp3 - ‘sp3_count’: Number of sp3 atoms - ‘hydrogen_count’: Number of added hydrogens (sp3_count * 2) - ‘is_perhydro’: True if fully saturated - ‘atom_to_locant’: Mapping from atom index to IUPAC locant (if
available). This IS the numbering the name is spelled from — consumers must inherit it rather than re-derive one.
’pcg_kind’: ‘carboxylic_acid’ | ‘ol’ | None — the senior ring principal characteristic group, which was given the lowest locants (see:func:_partial_sat_pcg)
’pcg_ring_atoms’: the ring atoms carrying it (empty when None)
- Return type:
Dict[str, Any] | None
Examples
>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCC2') # tetrahydronaphthalene >>> ri = mol.GetRingInfo >>> ring_atoms = set >>> for ring in ri.AtomRings: ... ring_atoms.update(ring) >>> result = detect_carbocyclic_partial_saturation(mol, ring_atoms) >>> result['prefix'] 'tetrahydro' >>> result['parent_name'] 'naphthalene'
- orthonym.rules.partial_saturation.is_tetrahydronaphthalene(mol, fused_ring_atoms)#
Check if fused system is tetrahydronaphthalene-type.
Tetrahydronaphthalene has: - 10 ring atoms total - 4 sp3 carbons (saturated ring) - 6 aromatic carbons (benzene ring) - All carbons (no heteroatoms)
- Parameters:
mol (Mol) – RDKit molecule
fused_ring_atoms (Set[int]) – Set of atom indices in the fused ring system
- Returns:
True if the system is tetrahydronaphthalene-type
- Return type:
bool
Examples
>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCC2') >>> ri = mol.GetRingInfo >>> ring_atoms = set >>> for ring in ri.AtomRings: ... ring_atoms.update(ring) >>> is_tetrahydronaphthalene(mol, ring_atoms) True
- orthonym.rules.partial_saturation.name_hydrogenated_fused_carbocycle(mol)#
Name a partially saturated fused bicyclic carbocycle (naphthalene-type).
This covers the V-5 / V2-theme defect: a fused carbocycle that is the hydrogenated form of a mancude parent (e.g. naphthalene) but still retains one or more isolated (non-aromatic) ring C=C double bonds. The legacy
_name_saturated_fused_carbocyclicblindly emits the fully-saturated (decahydro) name for any non-aromatic two-ring carbocycle, dropping the residual double bond and naming a different molecule.- Algorithm (IUPAC 2013:
Require a two-ring, all-carbon, non-aromatic system with >= 1 residual ring double bond (fully-saturated systems are handled as
perhydroelsewhere -> return None here).Identify the mancude parent by ring-atom count; require a populated
iupac_numberingmap (with lettered fusion locants).Enumerate every automorphic numbering of the parent skeleton onto the molecule (bond-order-agnostic substructure match), and pick the one giving the LOWEST locant set to the
hydroprefixes (the sp3 ring atoms), then to the residual double bonds.Emit
<locants>-<count>hydro<parent>(e.g.1,2,3,4,4a,5,6,8a-octahydronaphthalene).
Fails closed (returns None) for any system it cannot number correctly so it never emits a wrong name.
- Parameters:
mol (Mol) – RDKit molecule (a two-ring carbocyclic, non-aromatic fused system).
- Returns:
The hydro-prefixed parent name, or None if not handled (fail closed).
- Return type:
str | None
- orthonym.rules.partial_saturation.name_added_h_fused_carbocycle_suffix(mol)#
Name a mancude naphthalene bearing an -ol/-amine (di-) suffix that needs ‘added indicated hydrogen’ /, e.g.
naphthalen-4a(2H)-ol,naphthalen-4a(2H)-amine,naphthalene-2,4a(2H)-diamine,naphthalene-4a,8a-diol.The Blue Book gives these the added-indicated-hydrogen form as the PIN (the Blue Book), NOT the equivalent hydro form (
2,4a-dihydronaphthalen-4a-ol); both parse to the same structure through OPSIN, so the round-trip gate cannot choose between them and this producer must spell the PIN directly.This is the -ol/-amine sibling of:func:name_cyclic_oxo_compound: the -one suffix carbon is sp2 (exocyclic C=O) whereas the -ol/-amine suffix carbon is itself sp3, but the added-indicated-hydrogen mechanism max noncumulative double bonds; a pair of suffixes that removes a double bond needs no added H) is the same, so the maximum matching of the reduced ring atoms is reused.
Scope (fail-closed -> None otherwise): a single neutral non-radical fragment, no specified stereo, whose ring system is exactly the naphthalene skeleton (two ortho-fused 6-membered all-carbon rings, 10 atoms), NON-aromatic (an aromatic ring routes to the PAH partial-saturation path). The senior ring PCG must be an -ol or -amine (via:func:_partial_sat_pcg, which already scopes fail-closed to molecules whose only heteroatoms are the suffix O/N). Every sp2 ring atom must be covered by an intra-ring C=C (no exocyclic unsaturation), every suffix carbon must be sp3, and there must be NO hydro prefix (every reduced adjacent pair is a suffix pair removing a double bond) — the hydro-prefixed forms are a separate build.
- Numbering + added-indicated-H: the reduced (sp3) ring atoms are matched
adjacent pair = removed double bond -> no added H); an unmatched
reduced atom needs one added/indicated hydrogen, cited
(nH)after the suffix locant(s), UNLESS it is a ring-fusion carbon already bearing the suffix (its hydrogen is consumed by the suffix and marked only by the suffix locant). The naphthalene fixed numbering is chosen to give lowest locants to the suffix, then to the added indicated hydrogen. Fail-closed on any indeterminacy (the downstream / round-trip gate is the constitution backstop).
- orthonym.rules.partial_saturation.name_hydro_fused_chalcogen_suffix(mol)#
(BB 27935): -OOH on an sp3 carbon of a partially saturated fused carbocycle -> ‘<hydro-parent>-<locant>-peroxol’ (BB verbatim PIN: 1,2,3,4-tetrahydronaphthalene-1-peroxol).
Fail-closed (accuracy-first): exactly one -OOH, no other heteroatoms and no other substituents; the skeleton (molecule minus the two O) must be the numbering-verified tetralin family (names to ‘1,2,3,4-tetrahydronaphthalene’ via the normal pipeline) and the OOH carbon must sit at locant 1 (the sp3 ring carbon bonded to an aromatic fusion carbon). Structured so -OH can join later; this task ships only the -OOH case.
- orthonym.rules.partial_saturation.name_cyclic_oxo_compound(mol, pseudoketone_rings_equal=False)#
Name an UNSUBSTITUTED cyclic ketone / dione on a mancude ring system, emitting the preferred IUPAC name with added indicated hydrogen and/or hydro prefixes (IUPAC / / /.
- Worked examples (all OPSIN-2.9.0 round-trip + Blue-Book verified):
O=c1cccc[nH]1->pyridin-2(1H)-oneO=c1ccc2ccccc2[nH]1->quinolin-2(1H)-oneO=c1c2ccccc2[nH]c2ccccc12->acridin-9(10H)-oneO=C1CC=Cc2ccccc21->naphthalen-1(2H)-oneO=C1CCCc2ccccc21->3,4-dihydronaphthalen-1(2H)-oneO=C1C=CC(=O)c2ccccc21->naphthalene-1,4-dione(no added-H,O=c1[nH]c(=O)c2ccccc2[nH]1->quinazoline-2,4(1H,3H)-dioneO=C1C=Cc2ccccc21->1H-inden-1-one(intrinsic-IH parent)O=C1CCc2ccccc21->2,3-dihydro-1H-inden-1-one
Method: identify the mancude parent + numbering; the carbonyl C(s) take the -one/-dione suffix; ring atoms carrying an EXTRA hydrogen vs the mancude parent (an N-H or a >CH2) split, by a maximum matching of the ring graph, into hydro positions (matched pairs = reduced ring C=C) and added-indicated-H positions (unmatched, cited as
(nH)after the suffix locant). Lowest locants go to the suffix, then added-IH, then hydro /.- Tightly fail-closed (returns None — never a wrong name):
>= 1 ring carbonyl; UNSUBSTITUTED (ring + carbonyl O’s only);
the ring must retain residual unsaturation (an aromatic ring atom OR a non-carbonyl ring C=C) — a fully-saturated ring carbonyl is a saturated lactam/lactone/ketone named on the saturated parent (piperidin-2-one, oxolan-2-one), NOT here;
the whole molecule has no retained PIN name (so uracil / maleimide are left to their retained entries);
the mancude parent must resolve (aromatic monocycle, or a stored PAH / fused-heterocycle skeleton).
- orthonym.rules.partial_saturation.oxo_prefix_parent_numberings(mol, ring_atoms)#
Parent hydrides for a ring system whose ring C=O is NOT the principal characteristic group, so the =O is an ‘oxo’ PREFIX (the prefix-mode sibling of:func:name_cyclic_oxo_compound).
“Prefix nomenclature” (the Blue Book): “After the
introduction of indicated and ‘added indicated hydrogen’ atoms, all substituent groups not expressed as suffixes are cited as prefixes”. An oxo prefix substitutes the two hydrogen atoms of a saturated ring position, so every ring carbonyl carbon is a SATURATED position of the parent hydride, exactly like a ring >CH2 or >N-R: the parent’s own indicated hydrogen
:24639, “in preferred IUPAC names indicated hydrogen must always
be cited”) sits on one of them and the rest are hydro prefixes, cited in front of the parent. (PIN) examples: ‘9,10-dioxo-9,10- dihydroanthracene-2-carboxylic acid’ (:29471), ‘5,8-dioxo-5,6,7,8- tetrahydronaphthalene-2-carboxylic acid’ (:24890), ‘5-oxo-2,5-dihydrofuran- 2-carboxylic acid’ (:29276), ‘1,3-dioxo-1,3-dihydro-2H-isoindole-2,5-diyl’ (:24868), ‘2,2-dimethyl-1,3-dioxo-2,3-dihydro-1H-isoindol-2-ium’ (:41447), ‘2-methyl-4-oxo-3,4-dihydro-1H-2-benzoselenopyran-2-ium-3-ide’ (:42439).
Returns
[(parent_name, locant_map, ih_atoms, hydro_atoms),...]– one entry per mancude parent and numbering the structure allows – or ```` (fail closed).parent_namealready carries the hydro prefixes and the indicated hydrogen (‘3,4-dihydro-2H-1-benzopyran’, ‘1,4-dihydroquinoline’, ‘4H-1-benzopyran’); the caller chooses the numbering (b) indicated hydrogen, (c) suffixes, (e) hydro prefixes, (f) detachable prefixes).Scope (everything else returns ``
): ``ring_atomsis one whole fused (or monocyclic) ring system bearing >=1 ring C=O; every ring atom is neutral; every saturated ring position is a C=O carbon, an sp3 carbon or a neutral single-bonded nitrogen (no =NR / =S / =CR2 on the ring); the mancude parent resolves (_resolve_oxo_parent()); the parent’s indicated-hydrogen atoms are saturated in the molecule; the hydro count is even (a single left-over position needs ‘added indicated hydrogen’, which only a suffix position may carry,.
- orthonym.rules.partial_saturation.name_ring_ketone_with_added_indicated_h(mol)#
- orthonym.rules.partial_saturation.name_hydro_mancude_fused_carbocycle(mol)#
Name an UNSUBSTITUTED all-carbon FUSED ring system that is a hydro (part- or more-saturated) form of a mancude parent carrying INTRINSIC indicated hydrogen.
- Worked targets (all OPSIN-2.9.0 round-trip verified):
c1ccc2c(c1)CCc1ccccc1C2->10,11-dihydro-5H-dibenzo[a,d][7]annulene(dibenzosuberane; the amitriptyline / nortriptyline / protriptyline core)
C1=Cc2ccccc2CCC1->6,7-dihydro-5H-benzo[7]annulenec1ccc2c(c1)CCCCC2->6,7,8,9-tetrahydro-5H-benzo[7]annulene(benzosuberane; the benzsuberone precursor)
This is the suffix-free sibling of:func:name_cyclic_oxo_compound: it reuses the SAME mancude-parent resolution (
_resolve_oxo_parent()), but with NO ring characteristic group. The added ‘hydro’ positions are the sp3 ring carbons that are NOT the parent’s intrinsic indicated hydrogen, cited by lowest locants (b) then (e)) — they need not be an adjacent reduced-C=C pair, so no max-matching is used here. The mancude parent already spells its own intrinsic indicated hydrogen in its name (5H-..., (the Blue Book) /(:3721)); this function prepends the detachable, nonalphabetized
x,y-dihydrohydro prefix / (:1682)), placed just before the indicated hydrogen and numbered by lowest locants AFTER it. Order:[hydro]-[indicated H]-[parent].- Tightly fail-closed (returns None — never a wrong name):
one fragment, neutral, non-radical;
the WHOLE molecule is one edge-fused ring system: every heavy atom is a ring carbon and no ring atom bears an off-ring heavy neighbour, so the bare-parent name accounts for every atom (0 silent drop). A substituted drug (amitriptyline) therefore declines here and abstains — never wrong;
the mancude parent must resolve with a NON-EMPTY intrinsic indicated-H locant set — this scopes the path to the annulene / cyclopenta class and leaves the naphthalene-family tetralin path (no intrinsic IH) untouched;
there must be REAL added hydro (>= 1 reduced ring C=C); the bare mancude parent itself is named on the fused-heterocycle path (B1), not here;
the parent’s declared indicated-H locant(s) must land on sp3 (>CH2) ring atoms of the molecule, so the parent name’s
zH-already accounts for them and no moved / extra added-indicated hydrogen is needed (that more general case is deferred, fail closed).