orthonym.assembly.locant_omission#
Note
Internal API. Names and behaviour may change between releases.
– omission of locants. The ONE place the licences are decided.
Phase C tranche B. Pure module: RDKit mols in, booleans out. No I/O, no naming.
⚠ DENY BY DEFAULT. ```` “Citation of locants”
(the Blue Book Blue Book) is the rule:
- “In preferred IUPAC names, if any locants are essential for defining the structure
of the parent structure or of a unit of structure as defined by its appropriate enclosing marks, then all locants must be cited for the parent structure or that structural unit.”
```` (:2871) then grants narrow licences, and its own preamble says
“for absolute clarity in preferred IUPAC names it is necessary to be prescriptive
about when omission of locants is permissible.” So:
never write a locant-stripping pass – every omission is a positively-licensed structural predicate;
fail toward retaining the locant – anything this module cannot positively establish returns
False(orTruefrom:func:scope_forces_locants, which is the same direction);a licence is evaluated per enclosing-mark scope. One essential locant anywhere in a scope restores every locant in that scope – which is why
1-chloro-2-(pentafluoroethyl)benzeneelides inside the parentheses and keeps1,2outside.
★ THE SELF-VALIDATING BOUNDARY PAIR – the reason these predicates count HYDROGENS, never positions:
:7625 benzenehexol (PIN, (not benzenehexaol) -> OMITS
:54823 "Inositols, cyclohexane-1,2,3,4,5,6-hexols, are a specific
group of cyclitols." -> RETAINS
Same six OH, same ring size. A benzene ring carbon has ONE substitutable H, so six OH
completely substitutes the ring and ```` fires. A cyclohexane ring carbon
has TWO, so six OH is partial substitution and the :3009 counter-clause restores
every locant. A predicate that counts positions gets this pair wrong.
The class is OPEN (ring size, ring element and substituent identity are all unbounded), so a finite table of names or spellings would be wrong on its complement by construction. Structural predicates only.
- orthonym.assembly.locant_omission.CHALCOGENS = frozenset({'O', 'S', 'Se', 'Te'})#
:3007– “Except for hydrogen atoms attached to chalcogen atoms, such as in acids, alcohols,…”. Group 16: O, S, Se, Te (Po is not a nomenclature case).
- orthonym.assembly.locant_omission.substitutable_h_count(mol, idx)#
How many hydrogens on atom
idxare substitutable per:3007.Zero for a hydrogen on a chalcogen (acid/alcohol/thiol OH, SH, SeH, TeH) and zero for the H of an aldehyde formyl carbon; otherwise the atom’s total H count.
Returns
0– never raises – for a missing mol or an out-of-range index, so a caller that fails to establish the structure gets the deny-by-default answer.
- orthonym.assembly.locant_omission.substitutable_positions(mol)#
:3007. Atom indices carrying at least one substitutable hydrogen.
- orthonym.assembly.locant_omission.l5_uniform_complete(mol, *, decoration_of, counts=None)#
```` (
:3007) plus its counter-clause (:3009).- “All locants are omitted in compounds or substituent groups in which all
substitutable positions are completely substituted or modified, for example, by hydro, in the same way.”
- “In case of partial substitution or modification, all numerical prefixes must
be indicated.”
molis the parent hydride / parent compound, not the decorated molecule – the rule speaks of its substitutable positions.decoration_ofmaps an atom index of that parent to a decoration key (the prefix or suffix morpheme).True iff every substitutable position of
molhas all of its substitutable hydrogens decorated, and every decoration is the SAME KIND.countsis an optional per-atom decoration multiplicity, defaulting to 1. It exists becausedecoration_ofmaps one key per atom and cannot otherwise express a doubly decorated position –heptafluorobutanoic acid(:3017) puts two F on each of two CH2 carbons. Omitting it means “one decoration per atom”, so a position with two substitutable hydrogens is partial and the licence is denied: that is exactly thecyclohexane-1,2,3,4,5,6-hexolsboundary (:54823).Decorations at NON-substitutable positions are permitted and still count toward the “in the same way” test –
decahydronaphthalene(:3013) hydrogenates the two bridgeheads, which carry no substitutable H of their own.
- orthonym.assembly.locant_omission.l3_one_kind_of_substitutable_h(mol)#
```` (
:2939).- “The locant is omitted in monosubstituted symmetrical parent hydrides or parent
compounds where there is only one kind of substitutable hydrogen.”
True iff every substitutable hydrogen of
mollies in ONECanonicalRankAtoms(breakTies=False)orbit – benzene, cyclohexane, urea (:2943methylurea), pyrazine (:2949pyrazinecarboxylic acid).This speaks only about the parent. The caller must separately establish that the substitution really is mono and that nothing else in the scope forces locants (
scope_forces_locants()).
- orthonym.assembly.locant_omission.l4_no_isomer_by_relocation(mol, parent_atoms, *, prefix_locants, suffix_locants, stereo_text, has_indicated_h, has_isotope, is_multiplicative=False, is_ring_assembly=False, has_skeletal_replacement=False)#
§**** (
:2953) – the ISOMER-COUNT licence.- “Locants are omitted when no isomer can be generated by moving suffixes
and/or prefixes (if any) from their position to another or by interchanging them between two different positions.”
True => every locant in this scope is omitted.
molis the DECORATED molecule andparent_atomsthe atom indices of the parent hydride / parent compound within it; the decorations are then read off structurally (_l4_components()) rather than taken on trust.The test is exactly the rule’s own two operations, run to exhaustion: place the decoration multiset over every position of the parent that bears a hydrogen, in every way the positions’ hydrogen counts allow, and compare the resulting constitutions. One distinct constitution => no isomer can be generated => omit.
⚠⚠ ``substitutable_positions`` IS DELIBERATELY NOT REUSED HERE, and that is the single most important line of this function. That helper implements
:3007’s carve-out (“Except for hydrogen atoms attached to chalcogen atoms, such as in acids, alcohols, and to the carbon atoms of formyl groups”), which is a sentence of **** and has no counterpart in – this rule counts ISOMERS, and says nothing whatever about which hydrogens are “substitutable”. Routing through it would make the whole polysulfane family deny by construction: trisulfane isHS-S-SH, so EVERY hydrogen it has is on a chalcogen,substitutable_positionsis empty, andl3_one_kind_of_substitutable_h/l5_uniform_complete()/l6_all_substitutable_h_share_one_locant()therefore all deny – yet:39335printsCH3-S-S-SH methyltrisulfane (PIN)under §**** “Compounds with three or more contiguous identical chalcogen atoms are treated as parent hydrides in substitutive nomenclature”. Positions here are those bearing a hydrogen in the ORDINARY sense, and the reason no isomer exists for methyltrisulfane is a hydrogen COUNT, not a carve-out: the middle sulfur bears zero hydrogens, so S1/S3 are the only placements and they are one orbit. (⚠ And the:3007carve-out must NOT be loosened to make this work: it is load-bearing for, where propanedioic acid’s two acid O-H must not count orchloropropanedioic acid(:2951) loses its licence.)Derived against every example printed in the rule’s own block, positives and negatives, which is what fixes the two design points a simpler reading misses:
So single-orbit equivalence is NOT the test on either flank: it is too weak for a heterogeneous multiset (
:2995) and too narrow to notice co-location (:2999).⚠ The four spellings of
:3005– “As an exception the locant is not omitted from propan-2-one, butan-2-one, prop-2-enoic acid and prop-2-ynoic acid although unambiguous without a locant” – are exceptions to THIS licence (that sentence closes ‘s block). They are NOT enumerated here, deliberately: the set is demonstrably OPEN –prop-2-enamide (PIN)(:32724),...prop-2-enenitrile (PIN)(:46790) andprop-2-enoyl (preferred prefix)(:30570) are all equally unambiguous without a locant and all printed with one. (prop-2-enal’s locanted spelling appears at:35068only inside a[not 2-butylprop-2-enal...]clause that rejects the name on a different ground – parent selection – so it evidences the SPELLING, not a PIN endorsement; the three above carry the tag themselves.) A four-row table would therefore be wrong on its complement by construction. They stay correct because nothing routes an unsaturated chain suffix through this licence; a caller that ever does must handle the class, not the four names.Deny-by-default, per this module’s docstring. It declines on all THREE of the things
ARCH-a-licence-can-be-evaluated-on-the-wrong-molecule.mdrequires of a licence:locants_are_forced()andscope_has_isotopic_modification()here, and the fragment-boundary observation at its call site (kept inhandlers/_handler_shared.pyso this module stays a pure leaf). It also denies on any defined stereochemistry, since relocating a bond cannot be shown to preserve a descriptor.
- orthonym.assembly.locant_omission.l3_monosubstituted_locant_omitted(parent, *, n_substitutions, prefix_locants, suffix_locants, parent_cites_locants, stereo_text, has_indicated_h, has_isotope, is_multiplicative=False, is_ring_assembly=False, has_skeletal_replacement=False)#
§**** (
:2939) – the WHOLE licence, for ONE substitution.- “The locant is omitted in monosubstituted symmetrical parent hydrides or
parent compounds where there is only one kind of substitutable hydrogen.”
True => that single locant is omitted. The orbit test itself is NOT re-derived here: it is:func:l3_one_kind_of_substitutable_h, the one place the licence lives. This function adds the three things the orbit test deliberately leaves to its caller (its own docstring says so): that the substitution really is mono, that nothing else in the scope forces locants, and the two ambient scopes.
parentis the parent hydride or parent compound – the molecule with the ONE substitution REMOVED – because that is what the rule’s own examples measure:★ The boundary the whole task turns on, and it falls out of
:3007with no special case: propanedioic acid’s two acid O-H are on a chalcogen and are NOT substitutable, leaving C2 as the only kind, so the licence fires. Propane**diamide** has C2 and two amide N-H – neither a chalcogen H nor a formyl H, so they count – giving two kinds, so it is denied and2-methylpropanediamide(:2887) keeps its locant.:2889N1,N3-dimethylpropanediamide (PIN)proves independently that an amide N-H is substitutable. ⚠ Do NOT “fix” the chalcogen exclusion to make trisulfane work: it is load-bearing HERE, andmethyltrisulfaneis licensed by a different sub-rule, unimplemented) – see internal notes.⚠ This licence is orthogonal to (c) (
_ring_suffix_locant_is_trivial), which is restricted to saturated all-carbon monocycles and therefore cannot reach a heteroarene. L3 is what licensespyrazinecarboxylic acidwhilepiperidine-1-carbonitrile(:34730) correctly keeps its locant – piperidine’s N-H, C2/C6, C3/C5 and C4 are FOUR orbits. Neither rule may be widened into the other; the orbit predicate is the only thing separating those two rings.- Parameters:
parent – the parent hydride / parent compound, substitution removed.
n_substitutions – how many decorations sit on
parent. The rule says “monosubstituted”, so anything but exactly 1 denies. Passed separately from the locant lists because a decoration whose locant was already elided upstream contributes no locant, and counting locants alone would read a disubstituted parent as mono.prefix_locants – locants of substituent prefixes in this scope.
suffix_locants – locants of suffixes in this scope.
parent_cites_locants – True when the parent name itself already cites a locant – a heteroatom locant set (
1,4-dioxane), an added/indicated hydrogen, or an unsaturation locant. ```` (:2869) then restores every locant in the scope, so the licence declines. Every one of the Blue Book’s five printed L3 positives has a locant-free parent name (pyrazine, urea, disiloxane, coronene, propanedioic acid), so this is the deny-by-default side of a boundary the source does not print, and it is recorded as such rather than as a verified rule.
- orthonym.assembly.locant_omission.l3_locant_omitted_for_parent_atoms(mol, parent_atoms, *, prefix_locants, suffix_locants, parent_cites_locants, stereo_text, is_multiplicative=False, is_ring_assembly=False, has_skeletal_replacement=False)#
§**** (
:2939) where the parent is given as an ATOM SET.The one entry point both live call sites use. It performs the parent surgery – delete every atom outside
parent_atoms, letting RDKit restore the implicit hydrogens that the substituent had displaced – and derives the two arguments a caller must not be trusted with:n_substitutions, proven by:func:_one_substituent_removed;has_isotope, read off the real (undeleted) molecule, because the parent copy may not carry the label.
Returns False – retain the locant – on any failure to establish the structure, including a sanitization failure on the reconstructed parent.
⚠
parent_atomsis the parent hydride or compound in the rule’s sense: forpyrazinecarboxylic acidit is the six RING atoms only (the-carboxylic acidcarbon is the substitution), while forchloropropanedioic acidit is the chain plus both-COOHgroups (the chloro is the substitution). Getting that boundary wrong silently changes which molecule the orbit test measures.
```` (
:3031).- “All locants are omitted for parent compounds when all substitutable hydrogen
atoms have the same locant.”
Example
:3037difluoroacetic acid (PIN) (not 2,2-difluoroacetic acid)– acetic acid’s only substitutable hydrogens are the three on C-2, because the acid OH is a chalcogen H excluded by:3007.Fail-closed: a substitutable atom missing from
atom_to_locantdenies, because its locant cannot be shown to coincide.
- orthonym.assembly.locant_omission.l6_chain_parent_one_substitutable_atom(mol, chain_atoms, group_atoms=())#
```` (
:3031) for an ACYCLIC parent compound named by a producer from its chain and the atoms of the characteristic groups its name expresses (the acid / acyl halide / thioacid / peroxoic acid group, the anhydride or ester oxygen): “All locants are omitted for parent compounds when all substitutable hydrogen atoms have the same locant.” – ‘cyanoacetyl chloride (PIN)’ (:5112), ‘amino(oxo)ethaneperoxoic acid (PIN)’ (:30182), ‘S-methyl (ethylsulfanyl) (sulfanylidene)ethanethioate (PIN)’ (:32019); ‘acetamide’ keeps ‘2-’ because its N-H are substitutable too (’N-carbamoyl-2-phenylacetamide (PIN)’,:33364).The UNSUBSTITUTED parent compound is rebuilt on the graph: an atom of the chain, or a group atom bonded to the chain, has as many substitutable hydrogens as it carries now plus the bonds it has to atoms outside the chain and the groups (its substituents); hydrogens on chalcogens and on an aldehyde formyl carbon are not substitutable (
:3007). True only when exactly one atom has any.Deny-by-default,
:2869): False under:func:locants_are_forced, in an isotopically modified scope, with an isotope or a stereodescriptor on the chain, for a ring atom on the chain, or on any error.
- orthonym.assembly.locant_omission.l6_ring_parent_one_substitutable_atom(mol, ring_atoms)#
```` (
:3031) for a RING parent hydride named with its substituent prefixes only (no suffix): the unsubstituted ring’s substitutable hydrogens all sit on one ring atom – ‘chlorotrioxetane (PIN) (not 4-chloro-1,2,3-trioxetane)’ (:3187), ‘dichlorotrioxetane (PIN)’ (:3029). A ring atom’s substitutable hydrogens are its hydrogens plus its bonds to atoms outside the ring; hydrogens on chalcogens are not substitutable (:3007). Deny-by-default: forced locants, an isotopic scope, an isotope or a stereo mark on the ring, a charge, or any error.
- orthonym.assembly.locant_omission.mononuclear_parent_locant_required(features)#
True when a substituent on a ONE-ATOM chain parent must cite its locant ‘1’.
(the Blue Book) omits the locant ‘1’ “in substituted
mononuclear parent hydrides” (‘chloromethane (PIN)’) – parent hydrides only; a parent with a suffix (‘methanamine’, a functionalized parent hydride,
- 4770) is decided by (:2957):
“Locants are omitted when
no isomer can be generated by moving suffixes and/or prefixes (if any) from their position to another or by interchanging them between two different positions”; (:7304) “Locants are required for related compounds where additional substitutable positions are available, for example acetamide”. The positions of the parent compound are the carbon and the suffix nitrogen(s):
a C-prefix could move to a suffix N that holds a hydrogen – ‘1-hydrazinylmethanamine (PIN)’ (:38535);
an N-prefix could move to the carbon when the carbon holds a hydrogen and the prefix keeps the parent (
_movable_to_parent_carbon) – ‘(1E)-1-[…]-N-[…]methanimine (PIN)’ (:50452), ‘N,1-bis(4-chlorophenyl)- methanimine (PIN)’ (:26524); an acyclic-carbon N-substituent would change the parent, so ‘cyano-N,N-dimethylmethanamine N-oxide (PIN)’ (:36580) omits;with every position substituted nothing can move, and (:3007) omits (‘(Z)-N-hydroxy(4-chlorophenyl)(phenyl)methanimine (PIN)’,:47666).
Hydrogens on a chalcogen are not substitutable, so ‘chloromethanol’ keeps its omission. Hydrogens are read with their graph neighbours, so an explicit [H] that carries a double-bond descriptor counts. Carbon atoms of a group match are the substituents’ attachment atoms (a ketone match spans both flanking carbons), not positions of the parent; ring atoms belong to a ring substituent. (The carbon test is defensive and mutation-surviving today: every flanking carbon found on a one-carbon parent was a ring atom, which the ring test already skips – ‘cyclohexyl(phenyl)methanone’ is the witness for the pair.) Deny-by-default: anything not established returns False, which keeps the existing omission.
- orthonym.assembly.locant_omission.suffix_nitrogen_hydrogens_are_caps(movable=(True,))#
Declare that hydrogens of the suffix nitrogen in this naming are caps for substituents the caller adds back;
movableholds one flag per cap (see the comment above).
- orthonym.assembly.locant_omission.scope_forces_locants(*, prefix_locants, suffix_locants, stereo_text, has_indicated_h, has_isotope, is_multiplicative, is_ring_assembly, has_skeletal_replacement)#
```` (
:2869) – the deny-default itself.True => cite EVERY locant in this scope, licence or not.
Plain numeric locants alone never force: those are exactly what a ```` licence is permitted to omit. What forces is anything essential in the same scope – a letter or primed locant, a stereodescriptor that needs a locant, or one of the hard overrides of derivation Part C (indicated/added hydrogen; isotopic labels –
:44180§”” “if isotopic modification requires a locant to specify its position”; multiplicative names; ring assemblies; skeletal replacement –:6446§””, whose sentence “Once a structure modified by skeletal replacement (‘a’) prefixes has been named and numbered, it is considered to be a new parent hydride. As locants assigned to heteroatoms are essential, all locants must be cited as defined in “ is the override; the same line opens with the unrelated element-seniority order,F > Cl > Br > ..., but that is not the clause being cited here).Fail-closed: any argument that is
None– i.e. the caller could not establish it – returns True.
- orthonym.assembly.locant_omission.forced_locant_scope(reason)#
Declare that this naming scope contains an essential locant (````).
Every licence must consult:func:locants_are_forced and decline while this is active.
reasonis free text for debugging (e.g."isotope"); it is never parsed.
- orthonym.assembly.locant_omission.locants_are_forced()#
True when an enclosing:func:forced_locant_scope is active.
A licence that does not consult this will silently elide a locant the Blue Book requires, and neither (
namer.pystates verbatim that it “ignores isotopes”) nor the gold set can see it.
- orthonym.assembly.locant_omission.forced_locant_reason()#
The active reason, or None. Diagnostics only.
- orthonym.assembly.locant_omission.isotopic_naming_scope(reason='isotope')#
Declare that an isotopic descriptor will be spliced into this naming scope.
Entered UNCONDITIONALLY by
rules/isotopes.pyaround its skeleton naming, because the labels are stripped before naming and are therefore invisible to every structural test further down.
- orthonym.assembly.locant_omission.scope_has_isotopic_modification()#
True when an enclosing:func:isotopic_naming_scope is active.
A licence that would leave a scope with ZERO locants must decline on this ,
:44180). A licence whose locant-free form stays correct under(all candidate positions in one orbit) must NOT – see the comment
above for why this is separate from:func:locants_are_forced.
- orthonym.assembly.locant_omission.isotope_parent_positional_scope(reason='isotope')#
Declare that the PARENT skeleton being named carries a positional isotope label. Entered by
rules/isotopes.pyalongsideforced_locant_scope(), and only when a labelled atom sits on the parent hydride itself, so a licence that restores the parent’s OWN substituent-position locant can decline without over-citing the cases where the label sits in a substituent (see the module comment above).
- orthonym.assembly.locant_omission.parent_scope_has_positional_isotope()#
True when an enclosing:func:isotope_parent_positional_scope is active.
- orthonym.assembly.locant_omission.isotope_labelled_original_scope(original)#
Declare the labelled molecule whose isotope-stripped skeleton is being named (entered by
rules/isotopes.pywith:func:isotope_parent_positional_scope).
- orthonym.assembly.locant_omission.isotope_label_on_atoms(mol, atoms)#
True when a label of the declared labelled molecule sits on one of
atomsofmol(the isotope-stripped molecule being named): a labelled atom, or a labelled hydrogen bonded to it.molis mapped onto the labelled molecule by substructure match (every match, so a symmetric parent is read the same either way). False when no labelled molecule is declared ormoldoes not map.