orthonym.rules.polycyclics#

Note

Internal API. Names and behaviour may change between releases.

Polycyclic aromatic hydrocarbon (PAH) naming rules.

Handles identification and naming of common polycyclic aromatics: - Bicyclic: naphthalene - Tricyclic: anthracene, phenanthrene, fluorene, acenaphthene, acenaphthylene - Tetracyclic: pyrene, chrysene, tetracene, triphenylene, tetraphene, benzo[c]phenanthrene - Pentacyclic: pentacene, perylene, benzo[pqr]tetraphene - Hexacyclic+: coronene

Also coordinates with fused_rings module for fused heterocyclic systems.

IUPAC 2013 Rules (Blue Book Section: - PAH numbering is FIXED by IUPAC standard - Use retained names as parent - Substituents are named with their IUPAC locant position - Numbering is NOT reoriented based on substituents (unlike benzene)

Key difference from benzene: - Benzene substituent numbering uses lowest locants - PAH numbering is fixed to the standard IUPAC orientation

PAH Classification: - Ortho-fused: linear PAHs like naphthalene, anthracene, tetracene, pentacene - Peri-condensed: PAHs with interior atoms like pyrene, perylene, coronene

Integration with fused_rings.py: - This module handles carbocyclic PAHs (naphthalene, anthracene, etc.) - fused_rings.py handles fused heterocycles (indole, quinoline, etc.) - This module can delegate to fused_rings for heterocyclic detection

Name check: this module, polycyclics.py (plural), names fused polycyclic aromatic hydrocarbons by their retained names and FIXED numbering. The von Baeyer namer for bridged and caged ring systems is polycyclic.py (singular), with rules/polycyclic_von_baeyer.py as a thin wrapper around its VonBaeyerAnalyzer.

orthonym.rules.polycyclics.identify_polycyclic(mol)#
orthonym.rules.polycyclics.get_polycyclic_core_atoms(mol, pah_name)#

Get the atom indices that form the PAH core.

Parameters:
  • mol – RDKit Mol object

  • pah_name (str) – Name of the PAH (e.g., ‘naphthalene’)

Returns:

Set of atom indices forming the PAH core, or None if no match

Return type:

Set[int] | None

orthonym.rules.polycyclics.get_polycyclic_substituents(mol, pah_name, principal_group=None)#

Find substituents attached to a polycyclic aromatic core.

Parameters:
  • mol – RDKit Mol object

  • pah_name (str) – Name of the PAH (e.g., ‘naphthalene’)

  • principal_group (str | None) – the molecule-level principal group (features.principal_group); when it is a primary amine the amine’s ring carbon is treated as the PCG anchor for lowest-locant numbering (Fix 2).

Returns:

Dict mapping IUPAC locant (1-indexed) to list of substituent info dicts. Each dict has keys: ‘name’ (str), ‘atoms’ (list of atom indices)

Return type:

Dict[int, List[Dict]]

Note

Position mapping for naphthalene is based on IUPAC numbering: - Atoms are numbered 1-8 around the periphery - Fusion carbons (4a, 8a) are not substituent positions

orthonym.rules.polycyclics.get_polycyclic_iupac_locants(mol, pah_name, substituent_atoms=None, pcg_atoms=None)#

a phase: return authoritative IUPAC locants for a cataloged PAH.

Reads POLYCYCLIC_DATA[pah_name]['iupac_numbering'] (canonical-SMILES keyed) and translates canonical-atom indices to mol-atom indices via RDKit substructure match. String fusion locants (‘4a’, ‘10b’,…) are converted to (int, str) tuples at this boundary per a phase Decision (compare_locant_sets tuple-aware after Plan 01).

Returns a dict covering ALL ring atoms of the PAH, mixing plain int locants (peripheral) with (int, str) tuple locants (fusion atoms). Suitable for ring_info["iupac_locants"] in the cascade step 6 gate.

The 4 populated PAHs in v17 are: naphthalene (10 keys, 2 fusion tuples), anthracene (14 keys, 4 fusion tuples), phenanthrene (14 keys, 4 fusion tuples), pyrene (16 keys, 6 fusion tuples). Other entries with empty iupac_numbering (fluorene, acenaphthene,…) return None — a phase audit candidates.

Parameters:
  • mol – RDKit Mol object.

  • pah_name (str) – Canonical PAH name (key in POLYCYCLIC_DATA).

Returns:

Dict[int, int | (int, str)] when pah_name is a populated PAH. None when pah_name is not in POLYCYCLIC_DATA, its iupac_numbering is empty, or the substructure match fails.

Return type:

Dict[int, Any] | None

Source: https://iupac.qmul.ac.uk/BlueBook/P2.html (PAH numbering

is FIXED, not reoriented per substituents — match data directly).

Source: a phase internal notes (tuple encoding); (function form).

orthonym.rules.polycyclics.name_substituted_polycyclic(mol, pah_name, substituents, principal_group=None)#

Generate IUPAC name for a substituted polycyclic aromatic.

Parameters:
  • mol – RDKit Mol object

  • pah_name (str) – Name of the PAH parent (e.g., ‘naphthalene’)

  • substituents (Dict[int, List[Dict]]) – Dict from get_polycyclic_substituents

Returns:

IUPAC name string (e.g., ‘2-methylnaphthalene’)

Return type:

str

IUPAC Rules: - Locants are FIXED to IUPAC standard numbering - Alphabetize substituent prefixes - Use multiplicative prefixes (di-, tri-) for repeated substituents - Format: locants-substituent-parent - Functional groups as suffixes: parent-locant-suffix (e.g., naphthalene-2-carboxylic acid)

orthonym.rules.polycyclics.name_polycyclic(mol)#

Generate IUPAC name for a polycyclic aromatic hydrocarbon.

Main entry point for PAH naming. Handles: 1. Fully aromatic PAHs (naphthalene, anthracene, etc.) 2. Substituted PAHs (2-methylnaphthalene) 3. Partially saturated PAHs (tetrahydronaphthalene)

The routing order is: 1. Check for partially saturated carbocycles (tetrahydronaphthalene, etc.) 2. Check for fully aromatic PAHs 3. Return None if not a recognized PAH

Parameters:

mol – RDKit Mol object

Returns:

IUPAC name string if PAH identified, None otherwise

Return type:

str | None

Examples

>>> from rdkit import Chem
>>> mol = Chem.MolFromSmiles('c1ccc2ccccc2c1')
>>> name_polycyclic(mol)
'naphthalene'
>>> mol = Chem.MolFromSmiles('Cc1ccc2ccccc2c1')
>>> name_polycyclic(mol)
'2-methylnaphthalene'
>>> mol = Chem.MolFromSmiles('c1cc2ccc3cccc4ccc(c1)c2c34') # pyrene
>>> name_polycyclic(mol)
'pyrene'
>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCC2') # tetrahydronaphthalene
>>> name_polycyclic(mol)
'1,2,3,4-tetrahydronaphthalene'
orthonym.rules.polycyclics.name_partially_saturated_carbocycle(mol)#

Generate IUPAC name for a partially saturated carbocyclic fused system.

Thin wrapper over:func:name_partially_saturated_carbocycle_with_locants that discards the numbering. Prefer the sibling whenever the caller has to place its own locants — re-deriving a second numbering for the same name is a defect class, not an implementation detail (see that function’s docstring).

Handles compounds like tetrahydronaphthalene, dihydroanthracene, etc. These are PAH systems with some ring atoms saturated (sp3).

IUPAC 2013 format: [locants]-[prefix][parent] Example: 1,2,3,4-tetrahydronaphthalene

Parameters:

mol – RDKit Mol object

Returns:

IUPAC name string if partially saturated carbocycle detected, None otherwise.

Return type:

str | None

Examples

>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCC2')
>>> name_partially_saturated_carbocycle(mol)
'1,2,3,4-tetrahydronaphthalene'
class orthonym.rules.polycyclics.PartialSatName(name, atom_to_locant, spelled_offring_atoms)#

Bases: NamedTuple

What the partially-saturated fused-carbocycle producer publishes.

name alone is not enough to decorate: a consumer that adds substituent prefixes needs the numbering the name was spelled from AND the set of atoms the name already spells. Withholding either one produced a wrong name – the first gave 1-methyl- for a 2-substituted tetralin, the second gave 6-methyl-6-methyl-…. Both were caught by, i.e. both cost a correct name rather than shipping a wrong one.

name: str#

Alias for field number 0

atom_to_locant: Dict[int, Any]#

ORIGINAL atom idx -> IUPAC ring locant, incl. '4a'/'8a'.

spelled_offring_atoms: Set[int]#

Off-ring atoms name already accounts for (aromatic-ring substituent prefixes it built itself + the exocyclic atoms of its PCG suffix).

orthonym.rules.polycyclics.name_partially_saturated_carbocycle_with_locants(mol)#

The partially-saturated fused-carbocycle name TOGETHER WITH the atom_to_locant map it was spelled from and the off-ring atoms it already spells.

The map is the one:func:detect_carbocyclic_partial_saturation chose (ORIGINAL atom idx -> IUPAC ring locant, including the lettered fusion locants '4a' / '8a'), never a re-derivation. A consumer that has to place its own substituent or suffix locants MUST inherit this map: the hydro locants, the stereodescriptors, the PCG suffix and any downstream substituent prefix all have to agree on one numbering, and two independently-derived numberings for one name spell a different molecule. This mirrors the standing contract stated verbatim in vonbaeyer_universal.RingAnalysis.atom_to_locant and terminal_ring.TerminalRingName.numbering.

Parameters:

mol – RDKit Mol object

Returns:

A:class:PartialSatName, or None when this is not a partially saturated fused carbocycle this path can name correctly (fail closed).

Return type:

PartialSatName | None

Examples

>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCC2')
>>> name_partially_saturated_carbocycle_with_locants(mol).name
'1,2,3,4-tetrahydronaphthalene'
orthonym.rules.polycyclics.exocyclic_substituent_name(mol, sub_atoms, attach_idx, ring_atoms)#

The prefix of the substituent that hangs on a ring system by attach_idx (its atoms sub_atoms), or None. A substituted amino group with no ring in it (N-attached, more than one heavy neighbour) is spelled by the enumerator’s namer: the fragment namer reads such a fragment as a carbon chain and writes ‘aminomethyl’ for ‘methylamino’ and ‘amino-N-methylmethyl’ for ‘dimethylamino’ amines as prefixes, enclosing: ‘(dimethylamino)’, ‘ethyl(methyl)amino’; the guanidine is ‘carbamimidoylamino’,, the Blue Book). An N-aryl fragment (‘4-chloroanilino’) keeps the fragment namer, which spells it right (the enumerator’s best-effort spelling is ‘[(4-chlorophenyl)amino]’), and so does a nitrogen with a heteroatom neighbour: the enumerator reads N(OH)2 as ‘nitro’ (another molecule).