orthonym.rules.vonbaeyer_universal#
Note
Internal API. Names and behaviour may change between releases.
: universal von-Baeyer cage analysis.
Names ANY bridged/fused polycyclic cage — including AROMATIC cages — by
kekulizing a canonical copy and expressing every former-aromatic bond as an
explicit ene locant unsaturation). The emitted ...-polyene cage
re-parses (OPSIN) to a kekule structure whose canonicalization re-aromatizes
to the SAME molecule, so structural fidelity is preserved; this is the
universal ring fallback for the opt-in general engine ONLY. The default
PIN path’s aromatic-cage refusal (polycyclic.py:2789) is deliberately
untouched.
Determinism: kekulization is atom-order dependent, so we ALWAYS canonical- reparse first and work in canonical indices; results map back to original indices via GetSubstructMatch.
- orthonym.rules.vonbaeyer_universal.MAX_CAGE_ATOMS = 40#
Implementation ceilings on the cage this module will analyse. The Blue Book sets NO upper size limit on von Baeyer nomenclature is construction rules only), so both numbers are ours, not nomenclature’s. They guard only this module – the default PIN path (
polycyclic.name_polycyclic_complete) caps nothing abovering_count < 2.a phase measured what raising them buys, over the 1203 ring molecules of
benchmarks/pubchem_2000.csv, and the answer is NOTHING: with both caps set to 200 the emitted name is byte-identical for every molecule the caps touch. So the VALUES stay and the fix went to what was being COUNTED –MAX_CAGE_RINGSwas compared against RDKit’s symmetrized ring-set cardinality instead of the ring number (seepolycyclic.von_baeyer_ring_count), which made this cap refuse heptacyclo cages for being “more than 8 rings”.Do not raise
MAX_CAGE_RINGSwithout first fixing main-bridge selection:_find_main_ringonly ever offers a 0- or 1-atom main bridge, so 7 of 19 Blue Book von Baeyer PIN descriptors come back with a non-preferred main bridge (locked intests/unit/rules/test_v29_p2_vb_ring_count.py). Raising the cap would broaden that class, not add PIN coverage.
- orthonym.rules.vonbaeyer_universal.BEST_EFFORT_MAX_CAGE_ATOMS = 100#
The best-effort tier’s ceilings. A best-effort name need not be the PIN, only a verified systematic name, so the main-bridge concern above does not apply there; the work of each analysis is bounded by the per-name budgets (
fragment_naming._PERF_BUDGET/_ANALYSIS_CALL_BUDGET). The values are measured (TRIAGE ‘Large molecules – part 3’): with no ceiling at all, every ring system named in the large-molecule census, the large-polycycle ladder and its two corpora has at most 82 skeletal atoms and 11 rings; above 11 rings no system was named and the main-ring search of each one ran its perf budget out (census rows up to 210 s slower, the ladder’s 15- to 21-ring rows 30-44 s). The PIN tier keeps the two ceilings above.
- orthonym.rules.vonbaeyer_universal.cage_caps()#
(atoms, rings)ceilings for the tier of the running request: the best-effort values in a best-effort request (provenance.best_effort_request_ctx), elseMAX_CAGE_ATOMS/MAX_CAGE_RINGS– in a PIN or complete request, on every path, the best-effort recoveries such a request runs included, and outside a request.
- orthonym.rules.vonbaeyer_universal.beyond_pin_caps(n_atoms, n_rings=None)#
True iff a ring system of this size is analysed only under the best-effort ceilings. A name built on such a system is not labelled a PIN.
- orthonym.rules.vonbaeyer_universal.record_beyond_pin_caps(fragment)#
Record
fragment(the ring descriptor or ring name a producer built on a ring system beyond the PIN ceilings) as a part that is never a PIN, so a name carrying it is labelled below the PIN (provenance.record_non_pin_fragment).
- class orthonym.rules.vonbaeyer_universal.RingAnalysis(descriptor, total_atoms, hetero_prefix, unsaturation, cage_atoms, atom_to_locant, canon_match, is_mancude=False, hetero_per_atom=())#
Bases:
objectThe ONE field contract
_emit_ring_from_analysisconsumes.Why this base exists (a phase follow-up)#
UniversalCage(von Baeyer) andSpiroSystem(spiro) are two ring analysis forms feeding ONE shared emission tail, so the tail is written against a field shape rather than a class. That shape used to be stated TWICE –SpiroSystemre-declaredUniversalCage’s fields by hand – and the two copies drifted every time either producer was touched:: the skeletal-replacement TOTALITY gate was added to the spiro analyzer only, so a mercury von-Baeyer ring shipped as ``bicyclo[3.3.0]octane– a hydrocarbon name;: ``hetero_per_atomwas added to the cage only, so the spiro sibling kept theUNBOUND_MORPHEMEfinding that commit existed to remove.
Opposite directions, same cause: a contract maintained in parallel. It is declared here ONCE. The subclasses add NO fields – they exist for their names (repr,
isinstance, per-form docs), not to extend the shape – so a field added to the emission contract reaches BOTH producers by construction. Declaring a field is not populating it, so the other half of the guarantee is a test asserting each form actually fills it (tests/unit/validation/test_v29_p2_ring_parent_bindings.py).- descriptor: str#
- total_atoms: int#
- hetero_prefix: str#
- unsaturation: dict#
- cage_atoms: Tuple[int, ...]#
- atom_to_locant: Dict[int, int]#
- canon_match: Tuple[int, ...]#
- is_mancude: bool = False#
- hetero_per_atom: Tuple[Tuple[int, str], ...] = ()#
- class orthonym.rules.vonbaeyer_universal.UniversalCage(descriptor, total_atoms, hetero_prefix, unsaturation, cage_atoms, atom_to_locant, canon_match, is_mancude=False, hetero_per_atom=())#
Bases:
RingAnalysisvon Baeyer polycyclic analysis. Adds no field to
RingAnalysis.canon_matchhere is the canonical-copy index map: canon idx -> orig idx.
- orthonym.rules.vonbaeyer_universal.parse_von_baeyer_descriptor(descriptor)#
'tricyclo[3.3.1.1^3,7]'->([3, 3, 1], [(1, 3, 7)]); None if the string is not a well-formed von Baeyer descriptor.The three leading integers are the bicyclic system (two main-ring branches then the main bridge, “cited in descending numerical order”); each remaining term is
length^low,highfor one secondary bridge.
- orthonym.rules.vonbaeyer_universal.reconstruct_von_baeyer_skeleton(descriptor, secondary_order=None)#
Rebuild the ring skeleton a von Baeyer descriptor STRING denotes.
Returns
(total_atoms, frozenset{(low_locant, high_locant)})– the exact bond set the emitted name encodes, in locant space – orNonewhen the string is malformed.The numbering is not a convention of ours; it is read straight off the Blue Book, which is what makes this a proof rather than a heuristic:
** “Numbering bicyclic alicyclic hydrocarbons”** (
the Blue Book Blue Book): “The bicyclic ring system is numbered starting with one of the bridgeheads and proceeding first along the longer segment of the main ring to the second bridgehead, then back to the first bridgehead along the unnumbered segment of the main ring. Numbering is completed by numbering the main bridge beginning with the atom next to the first bridgehead.”** “Numbering the secondary bridge”** (
:9623): “After the main ring and main bridge have been numbered, the independent secondary bridge is numbered continuing from the higher numbered bridgehead of the main ring.”** “Numbering of secondary bridges”** (
:9711): “the numbering continues from the highest number of the main ring and main bridge. Each secondary bridge is numbered in turn starting with the independent secondary bridge linked to the highest numbered bridgehead atom of the main ring, then the independent secondary bridge linked to the next highest bridgehead atom, and so on. Each atom of a secondary bridge is numbered starting with the atom next to the higher numbered bridgehead.”
secondary_orderoverrides the order in which the secondary bridges consume locants. fixes that order by descending attachment bridgehead, but leaves ties to; the caller uses this to try the remaining orderings rather than false-reject a cage whose only deviation is a tie-break, which is a preference question and not a legality one.
- orthonym.rules.vonbaeyer_universal.audit_von_baeyer_descriptor(mol, cage_atoms, numbering, descriptor)#
Java-free structural correctness floor for a von-Baeyer descriptor.
Rebuild the ring skeleton the EMITTED DESCRIPTOR STRING denotes (Blue Book numbering rules,
reconstruct_von_baeyer_skeleton) and require SET EQUALITY with the input’s actual cage bond set in locant space. Any mismatch – a bridge the descriptor fails to cite, an atom count the brackets do not account for, or a numbering inconsistent with the string – returnsFalseso the caller fails closed.Why the string and not our own bridge list#
This audit used to reconstruct from
PolycyclicDescriptor.bridge_info_list– internal bookkeeping – rather than from the descriptor that actually gets spelled. Measured over 8,201 enumerated cages (N<=12) and 1,623 corpus cages, that was wrong in both directions at once:False rejections.
BridgeInfo.atomsfor a secondary bridge is stored in the opposite order to the bond path (numbering runs from the higher numbered bridgehead,, so the walk crossed a non-bonded pair and the audit refused 556/8,201 enumerated and 14/1,623 corpus cages whose emitted name was perfectly correct.False acceptances. The bridge list can agree with itself while the spelled string does not describe the cage at all – 6 corpus cages passed the old audit with a descriptor denoting a different skeleton.
Set-equality against the string catches both, and is what the name is actually judged on. It is deliberately independent of OPSIN: both downstream gates (the validity gate and) are documented FAIL-OPEN when Java is unavailable (
namer.py:588,:608), and with them off 30 enumerated cages ship a descriptor whose brackets account for fewer atoms than its own stem counts (e.g.tetracyclo[5.1.1.2^3,6]dodecane– 11 bracketed atoms,dodecane= 12). This floor refuses those without asking Java.This proves LEGALITY (the name denotes this molecule under this numbering), never PREFERENCE (that it is the PIN among legal alternatives).
- orthonym.rules.vonbaeyer_universal.analyze_cage_universal(mol, cage_atoms=None, allow_mancude=False, spiro_atom=None)#
Deterministic universal cage analysis; None on any refusal.
spiro_atom(default None, original-mol atom index) is the spiro-junction atom when this cage is a COMPONENT of a spiro ring system. When passed, the von-Baeyer numbering selection gives that atom the lowest locant, above the heteroatom criteria; it is mapped into the canonical index space used internally before being threaded toVonBaeyerAnalyzer.analyze. Every whole-molecule caller leaves it None, and the result is byte-identical.: when
allow_mancudeis True the aromatic/mancude-cage refusal below is LIFTED – the cage is kekulized (already done above) and every former-aromatic bond is emitted as an explicit von-Baeyer polyene ene locant unsaturation). When False (the default / PIN path) the refusal fires exactly as before, so that path is byte-identical.Every returned descriptor is put through
audit_von_baeyer_descriptor(a Java-free skeleton edge-audit): the descriptor+numbering must assert exactly the molecular-graph ring/bridge bond set or the cage is discarded (fail-closed). This floor holds for the saturated/valid tier too and is what makes the mancude polyene emission trustworthy independent of OPSIN.
- class orthonym.rules.vonbaeyer_universal.SpiroSystem(descriptor, total_atoms, hetero_prefix, unsaturation, cage_atoms, atom_to_locant, canon_match, is_mancude=False, hetero_per_atom=())#
Bases:
RingAnalysisSpiro ring-system analysis. Adds no field to
RingAnalysis.The field shape is INHERITED, not re-declared: this class used to restate
UniversalCage’s fields by hand and the two lists drifted twice (seeRingAnalysis).descriptoris"spiro[4.5]"/"dispiro[3.2.3.2]"andcanon_matchis the sorted original cage-atom tuple (the analysis works on a ring-only submol, so there is no separate canonical copy to map).
- orthonym.rules.vonbaeyer_universal.audit_spiro_descriptor(mol, cage_atoms, numbering, spiro_atoms, descriptor)#
Java-free structural correctness floor for a spiro descriptor+numbering.
Mirrors
audit_von_baeyer_descriptor’s set-equality contract, adapted to spiro topology. Fail-closed (False) on any of:- (bijection) numbering is not a 1-1 map of the cage atoms onto {1..N}
(rejects a numbering that maps two atoms to the same locant);
- (coverage) the SSSR rings contained in the cage do not union to exactly
the cage atom set (a ring atom silently dropped);
- (pure-spiro) two cage rings share >1 atom (fused/bridged mis-routed here),
a shared atom is not a declared spiro atom, a spiro atom is not in exactly 2 cage rings, a non-spiro atom is not in exactly 1, or n_rings != n_spiro + 1;
- (simple-cycle) any cage atom has != 2 ring-neighbours within one of its
rings (a real bond the descriptor’s cycle would assert is missing, or a bridge edge is present);
- (arithmetic) the descriptor’s segment integers + n_spiro != N (a wrong
ring-size descriptor for the audited skeleton).
- orthonym.rules.vonbaeyer_universal.analyze_spiro_universal(mol, cage_atoms=None, allow_mancude=False, free_valence_atoms=None, prefix_atoms=None)#
Deterministic universal spiro analysis; None on any refusal.
cage_atomsselects the spiro ring system insidemol(defaults to all ring atoms).free_valence_atoms(original indices) biases the monospiro numbering to give the free valence the lowest locant, substituent use).allow_mancudelifts the aromatic-spiro refusal, emitting the kekulized ene-locant form; when False an aromatic spiro fails closed (deferring to the retained-ring-name PIN path). Every result is put throughaudit_spiro_descriptor(fail-closed on any structural mismatch).