orthonym.rules.fusion_orientation#

Note

Internal API. Names and behaviour may change between releases.

Deterministic, coordinate-free orientation of fused polycyclic ring systems.

This module builds an INTEGER hex-lattice embedding of a fused ring system from its ring-fusion GRAPH (never from RDKit 2D coordinates / conformers, which are SMILES-seed-dependent and therefore forbidden here), then enumerates the 12 D6 symmetry orientations and scores each by the IUPAC 2013 preferred-orientation criteria (a)-(d).

Geometry (Stage 0/1 — all-six-membered systems)#

Each six-membered ring is drawn POINTY-TOP: two vertical edges on the left and right, vertices at top and bottom. A ring centered at integer lattice point (cx, cy) has its six atoms at the fixed offsets:

    (0, +2) top
(-1,+1) (+1,+1) upper-left / upper-right
(-1,-1) (+1,-1) lower-left / lower-right
    (0, -2) bottom

The vertical right edge of ring (cx, cy) — vertices (cx+1, cy+1) and (cx+1, cy-1) — coincides exactly with the vertical left edge of the ring centered at (cx+2, cy). An angular (upper-right) ring sits at (cx+1, cy+3); its lower-left edge coincides with ring (cx, cy)’s upper-right edge. Every ring center therefore lies on the triangular lattice generated by (2, 0) and (1, 3) and ALL atom coordinates are integers — no float, no coordinate eps comparisons.

The real Cartesian y is the lattice y times 1/sqrt(3) (a single common positive factor). Because that factor is positive and identical for every atom, it cancels in every sign/equality test used by the scoring, so all comparisons are done on the integer lattice coordinates directly.

Determinism guarantees#

  • Ring nodes are keyed by their minimum atom index (never set/dict iteration order); the BFS embedding seed is the ring with the lowest minimum atom index.

  • The 12 D6 transforms are a fixed ordered list.

  • Quadrant/row counts are exact integers (quarters x4, halves x2); the sqrt(3)/2 factor cancels.

  • On a tie over criteria (a)-(d) ALL surviving orientations are returned so the numbering cascade (fusion_numbering.py) can discriminate deterministically.

Source: IUPAC 2013 Blue Book (the Blue Book Blue Book ~line 12079).

orthonym.rules.fusion_orientation.build_ring_fusion_graph(rings, mol)#

Build the ring-fusion graph.

Nodes are rings, keyed by their position in the deterministically-sorted ring list (rings is sorted by sorted-atom-tuple in _sssr_rings). The key is therefore a unique, SMILES-order-INDEPENDENT integer. (Keying by the ring’s minimum atom index is NOT unique — two ortho-fused rings can share their minimum atom, e.g. naphthalene written `` has both rings minimal at atom 0 — so positions in the canonical sort are used.) Two rings are adjacent iff they share exactly one bond (two atoms) — ortho-(cata-)fusion.

Returns a dict node_key -> {'atoms': set, 'sorted_atoms': tuple, 'neighbours': {node_key: shared_bond_frozenset(2 atoms)}}.

orthonym.rules.fusion_orientation.classify_ring_system(mol, ring_atoms)#

Classify a ring system for Stage-1 eligibility.

Returns a dict with:

all_six: every SSSR ring is 6-membered carbocyclic: every ring atom is carbon cata_fused: ortho-(cata-)fused, no atom shared by 3+ rings

(equivalently no interior atom / no peri-fusion)

connected: the ring-fusion graph is connected rings: the SSSR rings (sorted atom tuples) graph: the ring-fusion graph

orthonym.rules.fusion_orientation.embed_atoms_on_hex_lattice(graph, mol)#

Embed every ring atom on the integer triangular lattice.

BFS over the ring-fusion graph from the seed ring (lowest min-atom-index) placed at lattice center (0, 0). Each neighbour ring is placed at the lattice direction whose shared edge matches the already-placed shared bond.

Returns {atom_idx -> (X, Y)} (integer lattice coords) or None if the geometry is inconsistent (e.g. peri-fusion the caller did not screen out, or a shared edge that does not align to any of the six lattice directions).

orthonym.rules.fusion_orientation.all_orientations(atom_coord)#

Return the 12 D6-transformed atom-coordinate maps (fixed order).

orthonym.rules.fusion_orientation.score_orientation(graph, coords)#

Score one oriented layout by (a)-(d).

Returns an integer tuple (-a, -b4, c4, -d2) where smaller is better:
a = max rings sharing the busiest horizontal row (most rings at one

true-y, requiring vertical shared bonds — guaranteed for all-6 pointy-top embeddings since same-row neighbours share a vertical edge), using the row that maximises the count;

b4 = 4 * (rings strictly in the upper-right quadrant) counted in quarters; c4 = 4 * (rings strictly in the lower-left quadrant) in quarters; d2 = 2 * (rings strictly above the horizontal row) in halves.

All counts are computed in EXACT integers. Ring true-centers are atom_sum / 6; to avoid fractions we scale every comparison coordinate by 6 (so we compare atom_sum directly) and the row axis by the same factor. The sqrt(3)/2 real-y factor cancels because it multiplies every y equally and we only test signs/equalities.

orthonym.rules.fusion_orientation.embed_general(graph, mol)#

Real-coordinate regular-polygon embedding for a mixed-ring cata-fused system (rounded to the integer grid). Deterministic: seed = lowest node key, BFS in sorted neighbour order. Returns None if inconsistent / non-injective (e.g. peri-fusion that slipped through).

orthonym.rules.fusion_orientation.all_orientations_general(coords)#

12 D6 orientations via REAL 2D rotation (k*60 deg + mirror), rounded to int — correct for the real-coordinate embed_general output.

orthonym.rules.fusion_orientation.score_orientation_general(graph, coords)#

Scale-/size-invariant (a)-(d) score (smaller is better). Rows = chains of rings joined by VERTICAL common bonds (shared-edge atoms with equal x); quadrant counts via exact integer cross-multiplication of the true ring centres (sum_x/n, sum_y/n).

orthonym.rules.fusion_orientation.best_orientations(mol, ring_atoms)#

Return ALL orientations tying for best by (a)-(d).

Returns None if the system is not embeddable. Otherwise a non-empty list of atom-coordinate maps. Dispatches by ring sizes:

  • all-six-membered -> the shipped integer HEX lattice + score_orientation (unchanged — S1/S2a path);

  • mixed (contains a 5- or 7-… ring), cata-fused, connected, no ring > 6 -> the S2b embed_general + REAL-rotation orientations. For a BICYCLIC system the orientation criteria do not discriminate a unique drawing, so ALL orientations are returned and the lowest-locant numbering cascade picks (heteroatom-driven); for >=3 rings the scale-invariant score_orientation_general selects the preferred orientation.