Skip to content

3.2

Choose a tag to compare

@stsouko stsouko released this 16 Sep 19:57

A feature release. Enhanced stereo collections now hold axes as well as centres, assign_cip() computes R/S where CIP rules 1a, 1b and 2 decide, and standardize_kekule() picks the Kekule form thiele() reads. One thing moves for an existing caller, named under Compatibility below.

A collection holds axes as well as centres, in one id space. 3.1 could put a tetrahedral centre in an OR or AND collection; a double bond, an allene or an atropisomer axis could not be a member of one. 3.2 stores a collection at its unit's anchor slot, exactly as a parity is stored, so an axis carries a group by the same mechanism a centre does and there is no second plane to keep in step with the first.

3.1 3.2
set_stereo_group(element, kind, group) an atom id an atom id or an owner pair
set_bond_stereo_group(n, m, kind, group) — the pair spelling under its own name
stereo_groups() members atom ids an atom id, or an ascending owner pair
bond_stereo_groups() — the pair-spelled view of the same ids
canonical_bond_stereo_groups() — that view with canonical ids
canonical_bond_stereo_group_ambiguities() — the keys the colouring cannot separate
has_bond_stereo_groups — True when any collection has an axis member
stereo_group_anchor_of(element) — the atom an atom-only format names
>>> m = smiles('F/C=C/C=C/F')
>>> m.set_bond_stereo_group(2, 3, 3, 1)      # AND 1
>>> m.set_bond_stereo_group(4, 5, 3, 1)      # the same collection
>>> m.stereo_groups()
{(3, 1): [(2, 3), (4, 5)]}

One namespace, two spellings. A bond group numbered 1 and an atom group numbered 1 are one collection, so a centre and an axis can be members of the same OR or AND set. bond_stereo_groups() is a view rather than a second store: it shares the ids stereo_groups() reports, and canonical_bond_stereo_groups() is a view of canonical_stereo_groups().

An axis is named by its owners, not by the atom its byte lives at. An allene's anchor is the chain midpoint, which is neither owner, so the members are the two terminals:

>>> a = smiles('CC=C=CC')
>>> a.set_stereo_group(3, 3, 1)              # stated on the chain midpoint
>>> a.stereo_groups()
{(3, 1): [(2, 4)]}
>>> a.stereo_group_anchor_of((2, 4))
3

Forgiving in, canonical out: a group stated on a pair no unit owns is kept as a label on the lower atom and logged edit:stereo-group-not-an-axis, never refused.

Compatibility. A caller that assumed every member of stereo_groups() is an int must now handle a 2-tuple, since a two-owner unit reports its owner pair. bond_stereo_groups() returns the axes alone if that is all you want, and one-owner members are still plain atom ids. Nothing in the canonical form moves: 3.1's keys are 3.2's keys.

Every format spells the axis it can, and says so where it cannot.

format how an axis is named
V3000 its chain midpoint, both directions — a bond collection where a bond joins the owners, an atom collection where none does
CXSMILES an owner atom, chosen by canonical position so the string is a function of the canonical form
MRV mrvStereoGroup on the anchor atom
pach v3/v4 a five-byte entry keyed on the owner pair, emitted in anchor order so decode and re-encode is byte-identical
V2000 one chiral bit per record; an AND or OR collection is reported v2000:enhanced-stereo-not-written
M  V30 MDLV30/STEBRAC1 BONDS=(2 2 4)      the diene above
M  V30 MDLV30/STERAC1 ATOMS=(1 3)         the allene, named at its midpoint
>>> format(m, 'x')
'C(=C\\C=C\\F)/F |&1:0,2|'
>>> MoleculeContainer.unpack(m.pack()).bond_stereo_groups()
{(3, 1): [(2, 3), (4, 5)]}

The pach v3 and v4 header field the earlier fifty stored records all leave zero is now read, and the two frozen corpora are byte-unchanged — the block is additive, not a layout change. docs/pach.rst carries the wire layout as executed samples.

CIP descriptors, where rules 1a, 1b and 2 decide. assign_cip() writes R and S over a hash-consed digraph ranked sphere by sphere, ordering by atomic number, a duplicate's distance from the root, and mass:

>>> c = smiles('C[C@H](O)F')
>>> c.assign_cip()
True
>>> c.atom_cips()
{2: 'R'}

It recomputes: a stored descriptor is never read as a hint, a tie-break or a cache, and the computed answer overwrites what an input stated, under cip:disagreed. A tetrahedral site those three rules tie, and any site with an aromatic bond in reach, keeps no descriptor and gets one cip:undecided line — the rules that settle those sites are rule 3 and up, so atom_cips() answering {} for a configured centre is an answer and the log says which one. A site with no parity gets neither a descriptor nor a line. Bond descriptors remain stated-only.

A double bond an alternating cycle can move is a Kekule choice. thiele() reads the drawing it is handed, so a compound with more than one Kekule form had one aromatic form per form. standardize_kekule() chooses first — the most double bonds inside a ring thiele() considers, and for a ring outside the 5–7 window the alternation with the smaller canonical form:

>>> x = smiles('CC1=CC=CC=CC=C1C')       # 1,2-dimethylcyclooctatetraene
>>> y = smiles('CC1=C(C)C=CC=CC=C1')     # the same compound, C1=C2 written double
>>> x.standardize_kekule(), y.standardize_kekule()
(True, False)
>>> x == y
True

Every form it chooses between is a valid molecule, so its two choice records are INFO and a trial that had to move a hydrogen or a charge is a failed trial; kekule-form:budget is LOST, a molecule that reaches the 512-kekulisation budget keeping whichever form it had. No configuration is stereogenic on a bond such a cycle can shift, so validate_stereo() drops the sign and the two shift drawings converge whether or not either states one.

Beside it: a ring that lent its double bonds takes them back before thiele(), a ring that borrows every double bond it has is counted rather than looked at, a donor flanked by two donors is not a Kekule aromatic, an ion of carbon that states its hydrogen has no room for a ring double, a substituted nitrogen carries the charge where it cannot carry the hydrogen, and two amidines sharing a nitrogen are one placement ranked in one frame.

Documentation. docs/stereo.rst is new: the anchor-and-owners rule, the per-format encoding table, and every log row on the read and the write side. docs/pach.rst gains the bond-group block, docs/molecule.rst the cip stage and the two records a narrower arena reports through from_bytes, docs/standardize.rst the kekule-form stage.