Skip to content

Commit

Permalink
Reactor stereo support (#165)
Browse files Browse the repository at this point in the history
stereo support implemented

tests required.
  • Loading branch information
stsouko committed Mar 4, 2021
1 parent bc458ef commit 1881deb
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 115 deletions.
125 changes: 59 additions & 66 deletions CGRtools/algorithms/stereo.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,62 +193,6 @@ def get_mapping(self: 'Container', other: 'Container', **kwargs):
else:
yield from super().get_mapping(other, **kwargs)

@cached_property
def _wedge_map(self: 'Container'):
plane = self._plane
atoms_stereo = self._atoms_stereo
allenes_centers = self._stereo_allenes_centers
atoms = self._atoms
used = set()
wedge = []
for n, s in self._allenes_stereo.items():
env = self._stereo_allenes[n]
term = self._stereo_allenes_terminals[n]
order = [(*env[:2], *term), (*env[1::-1], *term[::-1])]
if env[2]:
order.append((env[2], env[1], *term))
order.append((env[1], env[2], *term[::-1]))
if env[3]:
order.append((env[3], env[0], *term[::-1]))
order.append((env[0], env[3], *term))
order = sorted(order, key=lambda x: (x[0] in atoms_stereo, x[0] in allenes_centers,
-atoms[x[0]].atomic_number))
while (order[0][0], order[0][2]) in used:
order.append(order.pop(0))
order = order[0]
used.add((order[2], order[0]))
s = self._translate_allene_sign(n, *order[:2])
v = _allene_sign((*plane[order[0]], 1), plane[order[2]], plane[order[3]], (*plane[order[1]], 0))
if not v:
info(f'need 2d clean. wedge stereo ambiguous for atom {{{n}}}')
if s:
wedge.append((order[2], order[0], v))
else:
wedge.append((order[2], order[0], -v))

for n, s in atoms_stereo.items():
order = sorted(self._stereo_tetrahedrons[n], key=lambda x: (x in atoms_stereo, x in allenes_centers,
-atoms[x].atomic_number, atoms[x].in_ring))
while (order[0], n) in used:
order.append(order.pop(0))
used.add((n, order[0]))

s = self._translate_tetrahedron_sign(n, order)
# need recalculation if XY changed
if len(order) == 3:
v = _pyramid_sign((*plane[n], 0),
(*plane[order[0]], 1), (*plane[order[1]], 0), (*plane[order[2]], 0))
else:
v = _pyramid_sign((*plane[order[3]], 0),
(*plane[order[0]], 1), (*plane[order[1]], 0), (*plane[order[2]], 0))
if not v:
info(f'need 2d clean. wedge stereo ambiguous for atom {{{n}}}')
if s:
wedge.append((n, order[0], v))
else:
wedge.append((n, order[0], -v))
return tuple(wedge)

def _translate_tetrahedron_sign(self: 'Container', n, env):
"""
Get sign of chiral tetrahedron atom for specified neighbors order
Expand Down Expand Up @@ -409,8 +353,8 @@ def _stereo_cumulenes(self: 'Container') -> Dict[Tuple[int, ...], Tuple[int, int
nf = bonds[path[0]]
nl = bonds[path[-1]]
n1, m1 = path[1], path[-2]
if any(b.order not in (1, 4) for m, b in nf.items() if m != n1) or \
any(b.order not in (1, 4) for m, b in nl.items() if m != m1):
if any(b not in (1, 4) for m, b in nf.items() if m != n1) or \
any(b not in (1, 4) for m, b in nl.items() if m != m1):
continue
nn = [x for x in nf if x != n1 and atoms[x].atomic_number != 1]
mn = [x for x in nl if x != m1 and atoms[x].atomic_number != 1]
Expand Down Expand Up @@ -702,6 +646,62 @@ def _fix_stereo(self: 'MoleculeContainer'):
# flush cache
del self.__dict__['_MoleculeStereo__chiral_centers']

@cached_property
def _wedge_map(self: 'Container'):
plane = self._plane
atoms_stereo = self._atoms_stereo
allenes_centers = self._stereo_allenes_centers
atoms = self._atoms
used = set()
wedge = []
for n, s in self._allenes_stereo.items():
env = self._stereo_allenes[n]
term = self._stereo_allenes_terminals[n]
order = [(*env[:2], *term), (*env[1::-1], *term[::-1])]
if env[2]:
order.append((env[2], env[1], *term))
order.append((env[1], env[2], *term[::-1]))
if env[3]:
order.append((env[3], env[0], *term[::-1]))
order.append((env[0], env[3], *term))
order = sorted(order, key=lambda x: (x[0] in atoms_stereo, x[0] in allenes_centers,
-atoms[x[0]].atomic_number))
while (order[0][0], order[0][2]) in used:
order.append(order.pop(0))
order = order[0]
used.add((order[2], order[0]))
s = self._translate_allene_sign(n, *order[:2])
v = _allene_sign((*plane[order[0]], 1), plane[order[2]], plane[order[3]], (*plane[order[1]], 0))
if not v:
info(f'need 2d clean. wedge stereo ambiguous for atom {{{n}}}')
if s:
wedge.append((order[2], order[0], v))
else:
wedge.append((order[2], order[0], -v))

for n, s in atoms_stereo.items():
order = sorted(self._stereo_tetrahedrons[n], key=lambda x: (x in atoms_stereo, x in allenes_centers,
-atoms[x].atomic_number, atoms[x].in_ring))
while (order[0], n) in used:
order.append(order.pop(0))
used.add((n, order[0]))

s = self._translate_tetrahedron_sign(n, order)
# need recalculation if XY changed
if len(order) == 3:
v = _pyramid_sign((*plane[n], 0),
(*plane[order[0]], 1), (*plane[order[1]], 0), (*plane[order[2]], 0))
else:
v = _pyramid_sign((*plane[order[3]], 0),
(*plane[order[0]], 1), (*plane[order[1]], 0), (*plane[order[2]], 0))
if not v:
info(f'need 2d clean. wedge stereo ambiguous for atom {{{n}}}')
if s:
wedge.append((n, order[0], v))
else:
wedge.append((n, order[0], -v))
return tuple(wedge)

@property
def _chiral_tetrahedrons(self) -> Set[int]:
return self.__chiral_centers[0]
Expand Down Expand Up @@ -983,13 +983,6 @@ def __chiral_centers(self: Union['MoleculeContainer', 'MoleculeStereo']):
return chiral_t, {(n, m) for n, *_, m in chiral_c}, {path[len(path) // 2] for path in chiral_a}, morgan


class QueryStereo(Stereo): # todo: implement add_wedge
__slots__ = ()

def add_wedge(self, n: int, m: int, mark: bool, *, clean_cache=True):
raise NotImplementedError


# 1 2
# \ |
# \|
Expand All @@ -1014,4 +1007,4 @@ def add_wedge(self, n: int, m: int, mark: bool, *, clean_cache=True):
(2, 3): False, (3, 2): False, (2, 1): True, (1, 2): True}


__all__ = ['MoleculeStereo', 'QueryStereo']
__all__ = ['MoleculeStereo', 'Stereo']
4 changes: 2 additions & 2 deletions CGRtools/containers/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
from ..algorithms.components import StructureComponents
from ..algorithms.depict import DepictQuery
from ..algorithms.smiles import QuerySmiles
from ..algorithms.stereo import QueryStereo
from ..algorithms.stereo import Stereo
from ..periodictable import Element, QueryElement, AnyElement


class QueryContainer(QueryStereo, Graph, QuerySmiles, StructureComponents, DepictQuery, Calculate2DQuery):
class QueryContainer(Stereo, Graph, QuerySmiles, StructureComponents, DepictQuery, Calculate2DQuery):
__slots__ = ('_neighbors', '_hybridizations', '_atoms_stereo', '_cis_trans_stereo', '_allenes_stereo',
'_hydrogens', '_rings_sizes', '_heteroatoms')

Expand Down
17 changes: 5 additions & 12 deletions CGRtools/files/_mdl/write.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright 2020 Ramil Nugmanov <nougmanoff@protonmail.com>
# Copyright 2020, 2021 Ramil Nugmanov <nougmanoff@protonmail.com>
# This file is part of CGRtools.
#
# CGRtools is free software; you can redistribute it and/or modify
Expand Down Expand Up @@ -177,21 +177,14 @@ def __convert_cgr(g):

@classmethod
def __convert_query(cls, g):
bonds = g._bonds
atoms = {m: n for n, m in enumerate(g._atoms, start=1)}
wedge = defaultdict(set)
out = []
for n, m, s in g._wedge_map:
out.append(f'{atoms[n]:3d}{atoms[m]:3d} {bonds[n][m].order} {s == 1 and "1" or "6"} 0 0 0\n')
wedge[n].add(m)
wedge[m].add(n)
for n, m, b in g.bonds():
if m not in wedge[n]:
if len(b.order) > 1:
raise ValueError('supported only simple QueryBond')
out.append(f'{atoms[n]:3d}{atoms[m]:3d} {b.order[0]} 0 0 0 0\n')
props = []
if len(b.order) > 1:
raise ValueError('supported only simple QueryBond')
out.append(f'{atoms[n]:3d}{atoms[m]:3d} {b.order[0]} 0 0 0 0\n')

props = []
for n, m in g._neighbors.items():
if m:
i = len(props) + 1
Expand Down

0 comments on commit 1881deb

Please sign in to comment.