orthonym.rules.ring_replacement#
Note
Internal API. Names and behaviour may change between releases.
Total skeletal (‘a’) replacement-prefix construction for RING systems.
/ (von Baeyer) and (spiro): a skeletal non-carbon
ring atom is expressed by a replacement (‘a’) prefix carrying its locant –
3-oxabicyclo[2.2.1]heptane, 2-oxa-6-thiaspiro[4.5]decane.
Why this module exists#
The builder that used to live inline in polycyclic.py was partial, and
partiality here is a wrong-structure bug rather than a coverage gap. It gated
each ring atom on symbol in HETEROATOM_PREFIXES and skipped everything else,
so an off-table skeletal element contributed no morpheme – while the ring
stem still counted it (total_atoms = len(ring_atoms)). C1CC2CC[Hg]C2C1
therefore came back as bicyclo[3.3.0]octane: a hydrocarbon name for a
mercury ring. (name -> structure round trip) fails OPEN when the OPSIN
jar is absent – a supported mode – so nothing downstream caught it.
The primitive below returns the prefix together with the information a caller needs to fail closed:
unexpressed– every skeletal non-carbon ring atom the emitted prefix does not correctly express: an off-table element, an atom the numbering does not reach (no locant to cite), or an occurrence count past the multiplying- prefix table (which spelled the bare integer,"22sila").
Caller contract: refuse when ``unexpressed`` is non-empty. Never emit a name
whose ring stem counts an atom that no morpheme in it spells. Both universal
analyzers (vonbaeyer_universal.analyze_cage_universal and
analyze_spiro_universal) apply exactly this rule, so the two siblings no
longer disagree: before this module, the cage path dropped the atom and the
spiro path invented a morpheme for it (polycyclic_bridged’s
get_heteroatom_prefix falls back to symbol.lower + 'a', spelling
3-znaspiro[5.5]undecane).
The table is CLOSED, so fail-closed is the only sound design#
The Blue Book’s replacement-prefix set is Table 1.5, [BBv2:6436-6443]) and it is a fixed list, not a generative rule. verbatim: “Nondetachable prefixes, called ‘a’ prefixes, are used to designate the replacing skeletal atoms with their standard bonding number. Those related to these recommendations are listed in Table 1.5.” The wording is restrictive, so there is no way to derive a prefix for an arbitrary element: “element-blind” replacement is genuinely unachievable and refusing off-table elements is the correct end state rather than a temporary limitation.
(Earlier revisions of this docstring cited “ / Table 2.8” for the replacement set. Table 2.8 is “Retained names of heterocyclic parent ring components” [BBv2:11511] – an unrelated table. Corrected against the book.)
In particular Zn, Cd and Hg appear in NO replacement table – mercury was
explicitly DELETED by – and such rings are named by organometallic
nomenclature instead. So the mercury cage refusing here is right; do NOT add a
mercura prefix to make it name. (data/organometallics.METALLACYCLE_A_PREFIX
is the legitimate home of Hg/Zn/Cd.)
Three orders, three different element SETS – do not borrow across them#
This is the trap that governs which of Table 1.5’s 25 rows this module may emit. The Blue Book gives three seniority orders over ‘a’-prefix elements and they do NOT cover the same elements:
drops
At,PoandC; drops those three and the
four halogens. Emitting a replacement prefix needs BOTH a citation position and a
numbering rank, so the set this module may spell is the intersection –
‘s 18 elements, which is exactly HETEROATOM_PREFIXES below. The
remaining seven rows of Table 1.5 stay in TABLE_1_5 (the book’s table is
recorded whole) but are listed in VB_INADMISSIBLE with the reason, and they
fail closed through the ordinary unexpressed contract. Borrowing a
position for an element the von Baeyer rules never rank would be inventing a rule.
- Relationship to
rules/skeletal_replacement.py: that module is the ACYCLIC chain namer (
2,5,8-trioxanonane). Same nomenclature family, different
parent class and a different numbering source; they share no state. Its own
element table is deliberately NOT extended alongside this one: whether skeletal
replacement or the substitutive parent hydride (alumane / gallane /
indigane / thallane, Table 2.1 [BBv2:7924-7930]) is the PIN for a
Group-13 atom embedded in a CHAIN is a selection question this module does
not answer, and the chain path fails closed until it is answered.
Relationship to data/hw_heteroatoms.py: that is Table 2.4
, the Hantzsch-Widman monocycle context, which spells two of the
same elements DIFFERENTLY on purpose – aluma “(not alumina)” [BBv2:8245] and
indiga “(not inda)”, under a footnote reading “Compare with Table 1.5”
[BBv2:8250]. A prefix is therefore a function of (element, nomenclature
context), never of the element alone. Do not unify the two tables.
- class orthonym.rules.ring_replacement.ReplacementPrefix(prefix, per_atom, unexpressed)#
Bases:
objectResult of
build_replacement_prefix.prefixthe replacement block exactly as it is concatenated onto thering descriptor –
"3-oxa","2,4-dioxa","5-oxa-3-sila",""when nothing is expressed. NO trailing hyphen: the ‘a’-prefix attaches directly to the descriptor,2-oxabicyclo[2.2.2]octane). Always byte-identical to the legacy inline builder, INCLUDING its malformed >20 multiplier fallback – seeunexpressed.per_atom(atom_idx, morpheme)for every heteroatom the prefixspells CORRECTLY, ascending by atom index. One entry per atom, so a consumer can bind each spelled morpheme to the single atom it claims.
unexpressedskeletal non-carbon ring atomsprefixdoes not correctlyexpress, ascending. Non-empty means the caller must refuse.
per_atomindices andunexpressedare disjoint and together cover every non-carbon ring atom.- prefix: str#
- per_atom: Tuple[Tuple[int, str], ...]#
- unexpressed: Tuple[int, ...]#
- orthonym.rules.ring_replacement.vb_lambda_for_atom(mol, atom_idx)#
- λ bonding number /; placement; per-topology
/ / / for a ring ‘a’-prefix atom, or None.
Correction: earlier comments in this fix’s history cited for the λ-convention itself. That section heading is “If there is a choice of names and numbering…” (the Blue Book) – it governs CHOICE, not the λ symbol. The nonstandard-bonding-number concept is (standard) / (nonstandard); the symbol’s placement (immediately after the locant, no hyphen) is; and each parent-hydride topology has its own governing subsection: (acyclic), (monocyclic), (von Baeyer ring ‘a’-prefix – the context THIS function serves), (spiro).
Refines the shared
nonstandard_bonding_numberwith the SKELETAL-DEGREE rule that governs ring replacement nomenclature: when an ‘a’-replacement name is parsed, each skeletal atom takes the LOWEST valid valence >= its skeletal degree. So a high-degree (bridgehead) heteroatom whose actual valence EQUALS that connectivity-forced value needs NO λ – it is inferred, and an explicit λ there is redundant AND rejected (e.g. the bridgehead Te in heptatellurabicyclo[2.2.1]heptane has degree 3 and valence 4 = the lowest Te valence >= 3, so it must NOT carry λ4). λ IS cited only when the valence EXCEEDS the connectivity-forced value (the heteroatom carries extra hydrogen), e.g. a degree-2 ring S(IV): without the λ4 it would read as S(II) – a different molecule. This is intentionally LOCAL to the ring ‘a’-prefix path; the sharednonstandard_bonding_numberstays standard-valence-relative for the parent-hydride/chain namers (SF6 -> lambda6-sulfane needs λ even though its degree forces valence 6).
- orthonym.rules.ring_replacement.build_replacement_prefix(mol, numbering, ring_atoms)#
Build the ring skeletal-replacement prefix, TOTALLY.
- Parameters:
mol – RDKit Mol (may be a kekulized copy or a ring-only submol; indices must agree with
numberingandring_atoms).numbering (Dict[int, int]) – atom index -> ring locant (1-based).
ring_atoms (Set[int]) – the skeletal ring atoms of the system being named.
- Returns:
ReplacementPrefix.prefixis byte-identical to the string the inline builder inpolycyclic.pyproduced – for EVERY input, not only the well-formed ones, because three PIN callers concatenate it and withholding a part there would turn a malformed name into a heteroatom-dropping (wrong-structure) one.unexpressednames the atoms that string does not correctly express.- Return type: