orthonym.rules.benzene#

Note

Internal API. Names and behaviour may change between releases.

Benzene naming rules according to IUPAC 2013 (Blue Book).

Handles: - Monosubstituted benzenes (chlorobenzene, nitrobenzene) - Polysubstituted benzenes with numeric locants (1,4-dimethylbenzene) - Benzene ring orientation for lowest locants - Alphabetical ordering of substituents - Suffix functional groups on benzene (carboxylic acid, amide, sulfonamide, etc.)

IUPAC 2013 PIN Rules: - Numeric locants are REQUIRED (not ortho/meta/para) - Toluene is retained ONLY for unsubstituted methylbenzene - Substituted methylbenzene uses “methylbenzene” (not “toluene”) - Position 1 assigned to give lowest locants via first-point-of-difference - Ring-attached principal groups use suffix form - benzamide = retained name for C6H5CONH2

orthonym.rules.benzene.benzene_prefix_suffix_promotion(suffix_names, prefix_names, detected_fgs=None, has_amine_candidates=False)#

THE single authority on which detachable prefix becomes the ring suffix.

Two questions must give the same answer and were previously computed twice:

  1. name_substituted_benzene – which prefix does the NAME promote to the suffix? (a ring -OH is perceived as the prefix hydroxy and only becomes -ol here; likewise a promotable amine becomes -amine/aniline.)

  2. principal_group_ring_atoms – which ring atoms may criterion (c) therefore minimise the locants of?

⚠ Phase C Task 9b, root cause of C-2: (2) used to answer “the principal group has a prefix form that appears on this ring”, which is true for 76 of the 136 seniority names (every entry with both a ring suffix and a prefix) while (1) only ever promotes two of them. For the other 74, criterion (c) minimised the locant of a group the name still spells as a detachable prefix – INVERTING (f)/(g). Two answers that must agree and are computed twice is the defect shape, so both callers now read this one function.

Parameters:
  • suffix_names – the suffix names already present (suffix_groups keys).

  • prefix_names – the detachable prefix names present (prefix_groups keys).

  • detected_fgs – features.functional_groups, for the functional-class guard.

  • has_amine_candidates (bool) – whether any ring amine carries the amine_candidate payload _identify_nitrogen_group sets.

Returns:

(promoted_suffix, promoted_prefix_names). (None, frozenset) when no promotion happens. For the amine promotion the prefix set is empty because the promoted atoms are identified by the amine_candidate marker (the prefix may be amino, (N-methylamino),…), not by one name.

Return type:

Tuple[str | None, frozenset]

orthonym.rules.benzene.is_benzene_ring(mol, ring_atoms)#

Check if a ring is a benzene ring (6-membered aromatic carbocycle).

Parameters:
  • mol – RDKit Mol object

  • ring_atoms (Tuple[int, ...]) – Tuple of atom indices in the ring

Returns:

True if ring is benzene (6 aromatic carbons)

Return type:

bool

orthonym.rules.benzene.didehydro_benzene_name(mol, ring_atoms)#

(Wave-2 completion): a bare benzene ring in which 2 (or 4) ring carbons carry NO hydrogen (RDKit perceives benzyne as an aromatic C6 ring with a triple bond) is the didehydrobenzene parent: ‘1,2-didehydrobenzene’ (BB verbatim; was ‘benzene’ -> unknown).

Fail-closed: exactly 6 neutral ring carbons, no exocyclic heavy neighbour anywhere (substituted didehydrobenzenes are not built), every ring atom has 0 or 1 H, dehydro count in {2, 4}. Locants = the minimal tuple over all 12 ring walks.

orthonym.rules.benzene.get_benzene_ring(mol)#

Find the benzene ring in a molecule.

Parameters:

mol – RDKit Mol object

Returns:

Tuple of atom indices in the benzene ring, or None if not found

Return type:

Tuple[int, …] | None

orthonym.rules.benzene.get_benzene_substituents(mol, ring_atoms)#

Find substituents attached to each benzene carbon.

Parameters:
  • mol – RDKit Mol object

  • ring_atoms (Tuple[int, ...]) – Tuple of atom indices in the benzene ring

Returns:

Dict mapping ring atom index to list of substituent info dicts. Each dict has keys: ‘name’ (str), ‘atoms’ (list of atom indices)

Return type:

Dict[int, List[Dict]]

orthonym.rules.benzene.molecule_principal_group_and_fgs(mol)#

The molecule-level principal group AND the detected FGs, in ONE perception.

principal_group_ring_atoms needs both, and detecting the functional groups twice for one molecule is the only reason the two were ever separate. Routes through the same seniority.get_principal_group authority the namer uses, so the answer cannot diverge from features.principal_group. Fails closed to (None, None) (= legacy numbering) on any perception failure.

orthonym.rules.benzene.principal_group_ring_atoms(mol, ring_atoms, substituents, principal_group=None, detected_fgs=None)#

(c): the ring atoms bearing the PRINCIPAL characteristic group.

This is the input to orient_benzene’s criterion-0 tier. § “NUMBERING” (heading at the Blue Book) states at the Blue Book: “When several structural features appear in cyclic and acyclic compounds, low locants are assigned to them in the following decreasing order of seniority:”. That order ranks (c) principal characteristic groups and free valences (suffixes); (the Blue Book) four places ABOVE (f) detachable alphabetized prefixes, all considered together in a series of increasing numerical order; (the Blue Book) and five above (g) lowest locants for the substituent cited first as a prefix in the name; (the Blue Book). So the suffix locant set is minimised FIRST and the prefixes take what is left. Benzene has no fixed numbering (a, the Blue Book) and no indicated hydrogen (b, the Blue Book), so (c) decides whenever it applies.

Every caller used to derive this set from the is_suffix marker alone, which made it EMPTY for phenols: a ring -OH is still spelled as the prefix hydroxy when the ring is numbered and is promoted to the -ol suffix only later, inside name_substituted_benzene. Criterion (c) therefore never ran and criterion (f) handed locant 1 to the prefix – emitting 1-chlorobenzene-2,3,4,5,6-pentol for the PIN 6-chlorobenzene-1,2,3,4,5-pentol.

Whether the group IS promoted is not decided here: it is asked of benzene_prefix_suffix_promotion, the single authority name_substituted_benzene also reads.

⚠ Phase C Task 9b: this used to ask the seniority tables directly – “does get_suffix(pg, is_ring=True) exist and is get_prefix(pg) on this ring?” – which is a test for promotion ELIGIBILITY, not promotion. 76 of the 136 seniority entries have both a ring suffix and a prefix form and only two are ever promoted, so for the other 74 criterion (c) was minimising the locant of a group the name still spells as a detachable prefix, inverting (f) and (g). The decision is still LOCANT-FREE – it depends only on WHICH groups are present, never on where – which is what lets it be consulted BEFORE the ring is numbered.

is respected: only the SENIOR group is the principal characteristic

group. principal_group is the molecule-level answer from seniority.get_principal_group, so a ring carrying both -ol and -thiol yields the -ol atoms only (matching the emitted 2-sulfanylphenol, whose numbering hint previously contradicted the name by anchoring the thiol).

FAILS TOWARD CURRENT BEHAVIOUR: whenever the answer is not available (no principal group, no ring suffix for it, nothing promoted,…) this returns the legacy is_suffix union, so the change is a strict improvement rather than a coin flip. In particular a ring with NO principal characteristic group – Cc1c(C)c(C)c(C)c(C)c1Cl – has principal_group is None and returns the empty set, leaving criterion (c) vacuous and (f)+(g) legitimately in charge of 1-chloro-2,3,4,5,6-pentamethylbenzene.

Parameters:
  • mol – RDKit Mol object (unused today; kept so callers pass the full context and a future rule can consult the graph without a signature change).

  • ring_atoms (Tuple[int, ...]) – The benzene ring’s atom indices.

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

  • principal_group (str | None) – features.principal_group – the molecule-level PCG name from seniority.get_principal_group. None disables the promotion tier (legacy behaviour).

  • detected_fgs (Dict | None) – features.functional_groups, used only for the shared functional-class guard.

Returns:

Set of ring atom indices bearing the principal characteristic group; empty when criterion (c) does not apply.

Return type:

Set[int]

orthonym.rules.benzene.benzene_prefix_citation_locants(oriented, substituents, principal_group_positions=None)#

(g): the per-prefix locant sets, in alphabetical CITATION order.

§** “NUMBERING”** (the Blue Book Blue Book) criterion (g) (:3307) reads verbatim:

(g) lowest locants for the substituent cited first as a prefix in the name;

worked at :3315 4-methyl-5-nitrooctanedioic acid (PIN) and, decisively for a ring, at :3317 1-methyl-4-nitronaphthalene (PIN) (not 4-methyl-1-nitronaphthalene). The same rule is stated a second time, with a benzene worked example, as §** “Low locants are assigned to the prefix cited first in the name”** (:26085): 1-bromo-2-chloroethane (PIN) and 1-azido-4-isocyanatobenzene (PIN) (:26094).

“Cited first” is the alphanumerical order, so the sequence is keyed on the shared alpha_sort_key (di/tri ignored, iso/neo/cyclo/sec/tert included) rather than a hand-rolled sort. Every candidate orientation of one molecule carries the SAME set of prefix names, so only the locants differ and the returned tuple can be compared lexicographically.

A substituent is a prefix here unless it is the suffix:
  • atoms in principal_group_positions bear the principal characteristic group, so the group itself is criterion (c)’s business, not (g)’s – BUT any italic-N substituent it carries IS cited as a prefix in the name (N-(4-aminophenyl)-...) and therefore enters at that locant. Without this, two suffix instances distinguished ONLY by their N-substituents give an empty (g) key and the tie falls through to the canonical last resort, which orders by symmetry class rather than by citation – measured: it named Nc1ccc(Nc2ccc(Nc3ccccc3)cc2)cc1 with (4-aminophenyl) on the HIGHER nitrogen, though aminoanilino is cited before anilino.

  • an is_suffix substituent outside that set is a JUNIOR suffix that name_substituted_benzene demotes back to its own prefix form, so it IS cited as a prefix and enters under _SUFFIX_TO_PREFIX;

  • when no PCG set is supplied every is_suffix substituent is the suffix, so all of them are excluded.

For a single-instance suffix this is a no-op: criterion (c) has already fixed that locant, so the entry is identical in every surviving candidate.

Returns:

Tuple of locant tuples, ordered by the prefix’s citation position.

Return type:

Tuple[Tuple[int, …], …]

orthonym.rules.benzene.orient_benzene(mol, ring_atoms, substituents, principal_group_positions=None)#

Orient benzene ring to give lowest locants to substituents.

§** “NUMBERING”** (the Blue Book Blue Book) is an ORDERED cascade. Benzene has no fixed numbering (a, :3227), no indicated hydrogen (b, :3246), no added indicated hydrogen (d, :3270), no saturation/unsaturation choice (e, :3288) and no skeletal atom in a nonstandard valence state (h, :3320), so the applicable criteria are exactly (c), (f), (g) – and the cascade must then TERMINATE IN THE STRUCTURE, never in the order RDKit happened to enumerate the ring:

  1. (c) :3256 principal characteristic groups and free valences (suffixes) – lowest locant SET for the principal characteristic group, four places above (f). Applied only when principal_group_positions is supplied; None disables the tier.

  2. (f) :3301 detachable alphabetized prefixes, all considered together in a series of increasing numerical order, worked at :3305 (“the locant set ‘4,5,8’ is lower than ‘4,7,8’”) – first point of difference over the substituted positions.

  3. (g) :3307 lowest locants for the substituent cited first as a prefix in the name, worked at :3315 and :3317 (1-methyl-4-nitronaphthalene (PIN) (not 4-methyl-1-nitronaphthalene)); restated as § :26085 with the benzene PIN 1-azido-4-isocyanatobenzene (:26094). See benzene_prefix_citation_locants.

  4. last resort canonical symmetry-class orbits (_canonical_orbit_key) – a structure-derived invariant, so the answer cannot depend on how the molecule was spelled.

⚠ Phase C Task 9b: tiers 2 and 3 are new. Before them the cascade ended at (f) plus a crude “position 1 goes to the alphabetically first substituent AT position 1” heuristic, and fell through to candidate-enumeration order – which made the emitted name a function of the input SMILES. Tier 2 subsumes that heuristic: a (g) tie means every prefix holds the same locants in every surviving candidate, so the substituent at position 1 is the same too and the heuristic could not have discriminated either.

Monosubstituted rings short-circuit (the substituent is at locant 1), and an unsubstituted ring returns the input order because every orientation is the same name.

Parameters:
  • mol – RDKit Mol object (required for the canonical last-resort tier)

  • ring_atoms (Tuple[int, ...]) – Tuple of atom indices in the benzene ring (ordered)

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

  • principal_group_positions (Set[int] | None) – Optional set of ring atom indices bearing the principal characteristic group (c)). Default None = no PCG tier.

Returns:

List of ring atom indices reordered so position 1 is first

Return type:

List[int]

orthonym.rules.benzene.name_substituted_benzene(mol, ring_atoms, oriented_ring, substituents, detected_fgs=None)#

Generate systematic name for substituted benzene.

IUPAC 2013 PIN Rules: - Use numeric locants (not ortho/meta/para) for polysubstituted - Monosubstituted benzenes do NOT include locant (it’s always 1) - Alphabetize substituent prefixes - Use multiplicative prefixes (di-, tri-) for repeated substituents - Format: locants-substituent-benzene (or just substituent-benzene for mono) - Special case: benzonitrile (C6H5CN) uses suffix-style naming per - Ring-attached principal groups use suffix form

Parameters:
  • mol – RDKit Mol object

  • ring_atoms (Tuple[int, ...]) – Original ring atom tuple

  • oriented_ring (List[int]) – Oriented ring from orient_benzene

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

Returns:

IUPAC name string (e.g., “chlorobenzene” or “1,4-dimethylbenzene”), or None when any substituent is an unnameable sentinel (Wave2 T3a conservation guard: emitting without it would drop its atoms).

Return type:

str

orthonym.rules.benzene.name_benzene_derivative(mol)#

Generate name for a benzene derivative.

This is the main entry point for benzene naming.

Parameters:

mol – RDKit Mol object

Returns:

IUPAC name string, or None if not a benzene derivative

Return type:

str | None