orthonym.rules.substituent_purity#

Note

Internal API. Names and behaviour may change between releases.

Organyl substituent-prefix naming for the parent-hydride / oxoacid namers.

Single source of truth for the question “is this substituent an organyl that can be cited as a detachable prefix, and if so what is its prefix name?”. Used by the oxoacids (phosphonic / arsonic / stibonic …), the mononuclear hydrides (arsane / stibane / silane), the chalcogen chains, the polyazanes and their siblings.

Two functions, deliberately not interchangeable:

organyl_prefix_name

The NAMING primitive. Delegates to the audited shared chokepoint assembly.substituent_enumerator.name_substituent — the same 5-tier cascade every general_engine locus uses — behind a strict acceptance filter, so a fragment the general machinery can already name honestly is no longer refused by a private approximation of it.

is_simple_unbranched_organyl

A narrow ROUTING predicate. True only for the small class the retired private walker accepted (unbranched alkyl attached at a terminus, or phenyl/naphthyl). Kept for the one call site that uses the guard to choose which producer handles a fragment rather than to name it: widening a routing test silently changes which handler claims a molecule.

There is deliberately no third, deprecated entry point. pure_organyl_prefix_name existed only as a shim while the nine families migrated (a phase); all 23 of its call sites now use one of the two functions above, so it was deleted rather than left with zero callers – a zero-caller shim is how the next session acquires new ones. _narrow_walk_name stays private and backs only the predicate.

What this module used to be, and why it changed#

It used to wrap rules.phosphorus._characterize_substituent, which follows ONLY the carbon skeleton and therefore (a) silently DROPS a hanging heteroatom (2-hydroxyethyl -> “ethyl”), (b) cannot tell propan-1-yl from propan-2-yl (isopropyl -> “propyl”), and (c) miscounts a ring or benzyl as a linear alkyl (cyclohexyl -> “hexyl”, benzyl -> “heptyl”). A purity walk here rejected every one of those, so the guard was correct — but the four refusals were artefacts of the walker, not of nomenclature, and they fail-closed nine families at once. The chokepoint has no such artefacts (it spells benzyl, cyclohexylmethyl, propan-2-yl, naphthalen-1-yl), so the walker is retired in its favour.

The bounded element set (and the reason for it)#

organyl_prefix_name accepts a fragment built only from carbon, hydrogen and halogen, and fail-closes on every other element. This is a nomenclature constraint, not a walker artefact:

  • lets the principal characteristic group, not the hub, decide the parent. A heteroatom in the organyl may carry a suffixable characteristic group that outranks the hub’s, in which case citing the organyl as a mere prefix names a non-preferred structure — HO-CH2CH2-NH-NH2 is 2-hydrazinylethan-1-ol, not (2-hydroxyethyl)hydrazine, and HOOC-CH2-P(=O)(OH)2 puts the carboxylic acid (senior inside class 7) in the suffix.

  • A hydrocarbon fragment has no characteristic group at all, and the halogens are cited only as prefixes, Table 28), so neither can ever displace the hub’s principal group. Widening to them is therefore safe at every call site without a per-family seniority argument.

Extending the class to heteroatom organyls needs that per-family seniority comparison; until it exists those fragments keep failing closed.

The stereo-expression obligation (-FIX Item 1)#

Widening the class widened it to STEREOGENIC fragments, and the chokepoint’s generic stereo emitter (assembly.substituent_naming._add_substituent_stereo) reads only atom _CIPCode — never bond _CIPCode, as its own sibling states verbatim at substituent_naming.py:150-152. So C/C=C/[AsH2] and C/C=C\[AsH2] — two different compounds — both came back prop-1-en-1-yl and both shipped (prop-1-en-1-yl)arsane. Nothing downstream could catch it:

compares the InChIKey skeleton block, which excludes stereochemistry

by construction, and namer._final_stereo_check’s backstop is log-only for these handlers (it printed the correct diagnosis and returned the name anyway).

The repair has two halves, and both are needed:

  1. Express what is locatable. substituent_naming._name_unsaturated_chain is the one producer that holds the substituent’s own numbering, so it now emits the full located block via _located_stereo_block — (1E)-prop-1-en-1-yl, (2E)-but-2-en-1-yl, (2R,3E)-pent-3-en-2-yl. All three shapes are verbatim Blue Book PIN tokens; see that function’s docstring for the citations and their section headings.

  2. Prove the rest and fail closed. organyl_prefix_name requires the name to express EXACTLY as many descriptor tokens as the fragment defines stereo elements. What that still refuses is the multi-centre acyclic case (CC[C@@H](C)[C@@H](C)[As](C)C), where ### `` demands a locant per descriptor and forbids fabricating one — so the two diastereomers now refuse instead of colliding on ``3-methylpentan-2-yl.

Ring stereo needed no work: the ring path already emitted (2S,5R)-2,5-dimethylcyclohexyl and the obligation confirms it.

orthonym.rules.substituent_purity.organyl_prefix_name(mol, start_idx, exclude_idx)#

Return the detachable prefix name for the organyl rooted at start_idx and bonded to exclude_idx, or None (fail-closed).

start_idx is FRAGMENT-side and exclude_idx is its parent-side neighbour — the dominant attach_idx convention of the shared chokepoint. The convention is verified rather than trusted (the codebase holds one documented violation of it, SubstituentInfo.attach_mol_idx), so a call whose two indices are not bonded fail-closes with a warning instead of naming some unrelated subtree.

Pure: no mol mutation.

orthonym.rules.substituent_purity.is_simple_unbranched_organyl(mol, start_idx, exclude_idx)#

True iff the substituent is an unbranched alkyl attached at a terminus, or a phenyl/naphthyl aryl — the narrow class the retired walker accepted.

A ROUTING predicate, not a namer. Used where the answer selects a different producer (the contracted alkoxy-prefix builder), whose behaviour on ring-bearing, branched or unsaturated input is not established; widening the test there would change which producer claims a molecule rather than fix a refusal.