Skip to content

Commit

Permalink
!QUERY BONDS UPDATE!
Browse files Browse the repository at this point in the history
now queries supports bonds lists.

fixed several bugs.
  • Loading branch information
stsouko committed Dec 9, 2020
1 parent cf9e362 commit 3c3701b
Show file tree
Hide file tree
Showing 13 changed files with 226 additions and 283 deletions.
7 changes: 2 additions & 5 deletions CGRtools/algorithms/calculate2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ def __prepare(self, component, randomize, c_stiff, r_stiff):
continue
for m, b in m_bond.items():
if n not in dd[m]:
b = b.order
if b == 8 or len(m_bond) > 4:
dd[n][m] = dd[m][n] = True
else:
Expand All @@ -303,8 +302,7 @@ def __prepare(self, component, randomize, c_stiff, r_stiff):
# for angle = 180'
if len(m_bond) == 2:
(a1, bond1), (a2, bond2) = m_bond.items()
order1, order2 = bond1.order, bond2.order
if order1 == order2 == 2 or order1 == 3 or order2 == 3:
if bond1 == bond2 == 2 or bond1 == 3 or bond2 == 3:
straights.append((mapping[a1], mapping[a2]))

for n, m_bond in dd.items():
Expand Down Expand Up @@ -483,8 +481,7 @@ class Calculate2DMolecule(Calculate2D):
__slots__ = ()

def _is_angle(self, bond1, bond2):
order1, order2 = bond1.order, bond2.order
return not (order1 == order2 == 2 or order1 == 3 or order2 == 3 or order1 == 8 or order2 == 8)
return not (bond1 == bond2 == 2 or bond1 == 3 or bond2 == 3 or bond1 == 8 or bond2 == 8)


class Calculate2DCGR(Calculate2D):
Expand Down
16 changes: 7 additions & 9 deletions CGRtools/algorithms/components/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from CachedMethods import cached_property
from collections import defaultdict
from typing import Tuple
from ...exceptions import ValenceError


class StructureComponents:
Expand All @@ -32,8 +31,8 @@ def aromatic_rings(self) -> Tuple[Tuple[int, ...], ...]:
Aromatic rings atoms numbers
"""
bonds = self._bonds
return tuple(ring for ring in self.sssr if bonds[ring[0]][ring[-1]].order == 4
and all(bonds[n][m].order == 4 for n, m in zip(ring, ring[1:])))
return tuple(ring for ring in self.sssr if bonds[ring[0]][ring[-1]] == 4
and all(bonds[n][m] == 4 for n, m in zip(ring, ring[1:])))

@cached_property
def cumulenes(self) -> Tuple[Tuple[int, ...], ...]:
Expand Down Expand Up @@ -79,10 +78,9 @@ def tetrahedrons(self) -> Tuple[int, ...]:
for n, atom in atoms.items():
if atom.atomic_number == 6 and not charges[n] and not radicals[n]:
env = bonds[n]
if all(x.order == 1 for x in env.values()):
b_sum = sum(x.order for x in env.values())
if b_sum > 4:
raise ValenceError(f'carbon atom: {n} has invalid valence = {b_sum}')
if all(x == 1 for x in env.values()):
if sum(int(x) for x in env.values()) > 4:
continue
tetra.append(n)
return tetra

Expand All @@ -97,14 +95,14 @@ def _cumulenes(self, heteroatoms=False):
if atom.atomic_number in atoms_numbers:
adj_n = adj[n].add
for m, bond in bonds[n].items():
if bond.order == 2 and atoms[m].atomic_number in atoms_numbers:
if bond == 2 and atoms[m].atomic_number in atoms_numbers:
adj_n(m)
else:
for n, atom in atoms.items():
if atom.atomic_number == 6:
adj_n = adj[n].add
for m, bond in bonds[n].items():
if bond.order == 2 and atoms[m].atomic_number == 6:
if bond == 2 and atoms[m].atomic_number == 6:
adj_n(m)
if not adj:
return ()
Expand Down
11 changes: 5 additions & 6 deletions CGRtools/algorithms/depict.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,27 +937,26 @@ def _render_bonds(self):
triple_space = config['triple_space']
dash3, dash4 = config['aromatic_dashes']
for n, m, bond in self.bonds():
order = bond.order
nx, ny = plane[n]
mx, my = plane[m]
ny, my = -ny, -my
if order == 1:
if bond == 1:
svg.append(f' <line x1="{nx:.2f}" y1="{ny:.2f}" x2="{mx:.2f}" y2="{my:.2f}"/>')
elif order == 4:
elif bond == 4:
dx, dy = rotate_vector(0, double_space, mx - nx, ny - my)
svg.append(f' <line x1="{nx + dx:.2f}" y1="{ny - dy:.2f}" x2="{mx + dx:.2f}" y2="{my - dy:.2f}"/>')
svg.append(f' <line x1="{nx - dx:.2f}" y1="{ny + dy:.2f}" x2="{mx - dx:.2f}" y2="{my + dy:.2f}" '
f'stroke-dasharray="{dash3:.2f} {dash4:.2f}"/>')
elif order == 2:
elif bond == 2:
dx, dy = rotate_vector(0, double_space, mx - nx, ny - my)
svg.append(f' <line x1="{nx + dx:.2f}" y1="{ny - dy:.2f}" x2="{mx + dx:.2f}" y2="{my - dy:.2f}"/>')
svg.append(f' <line x1="{nx - dx:.2f}" y1="{ny + dy:.2f}" x2="{mx - dx:.2f}" y2="{my + dy:.2f}"/>')
elif order == 3:
elif bond == 3:
dx, dy = rotate_vector(0, triple_space, mx - nx, ny - my)
svg.append(f' <line x1="{nx + dx:.2f}" y1="{ny - dy:.2f}" x2="{mx + dx:.2f}" y2="{my - dy:.2f}"/>')
svg.append(f' <line x1="{nx:.2f}" y1="{ny:.2f}" x2="{mx:.2f}" y2="{my:.2f}"/>')
svg.append(f' <line x1="{nx - dx:.2f}" y1="{ny + dy:.2f}" x2="{mx - dx:.2f}" y2="{my + dy:.2f}"/>')
else:
else: # other query bonds
svg.append(f' <line x1="{nx:.2f}" y1="{ny:.2f}" x2="{mx:.2f}" y2="{my:.2f}" '
f'stroke-dasharray="{dash1:.2f} {dash2:.2f}"/>')
return svg
Expand Down
4 changes: 2 additions & 2 deletions CGRtools/algorithms/smiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@


charge_str = {-4: '-4', -3: '-3', -2: '-2', -1: '-', 0: '0', 1: '+', 2: '+2', 3: '+3', 4: '+4'}
order_str = {1: '', 2: '=', 3: '#', 4: ':', 8: '~', None: '.'}
order_str = {1: '-', 2: '=', 3: '#', 4: ':', 8: '~', None: '.'}
organic_set = {'C', 'N', 'O', 'P', 'S', 'F', 'Cl', 'Br', 'I', 'B'}
hybridization_str = {4: 'a', 3: 't', 2: 'd', 1: 's', None: 'n'}
dyn_order_str = {(None, 1): '[.>-]', (None, 2): '[.>=]', (None, 3): '[.>#]', (None, 4): '[.>:]', (None, 8): '[.>~]',
Expand Down Expand Up @@ -440,7 +440,7 @@ def _format_atom(self, n, **kwargs):
return ''.join(smi)

def _format_bond(self, n, m, **kwargs):
return order_str[self._bonds[n][m].order]
return ','.join(order_str[x] for x in self._bonds[n][m].order)


class QueryCGRSmiles(Smiles):
Expand Down
2 changes: 1 addition & 1 deletion CGRtools/algorithms/stereo.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def _translate_tetrahedron_sign(self, n, env):
:param env: neighbors order
"""
s = self._atoms_stereo[n]
return self._translate_tetrahedron_sign_reversed(n , env, s)
return self._translate_tetrahedron_sign_reversed(n, env, s)

def _translate_tetrahedron_sign_reversed(self, n, env, s):
order = self._stereo_tetrahedrons[n]
Expand Down
89 changes: 88 additions & 1 deletion CGRtools/containers/bonds.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# along with this program; if not, see <https://www.gnu.org/licenses/>.
#
from typing import Optional
from .._functions import tuple_hash


class Bond:
Expand All @@ -32,6 +33,8 @@ def __init__(self, order):
def __eq__(self, other):
if isinstance(other, Bond):
return self.__order == other.order
elif isinstance(other, int):
return self.__order == other
return False

def __repr__(self):
Expand All @@ -52,6 +55,14 @@ def copy(self) -> 'Bond':
copy._Bond__order = self.__order
return copy

@classmethod
def from_bond(cls, bond):
if isinstance(bond, cls):
copy = object.__new__(cls)
copy._Bond__order = bond._Bond__order
return copy
raise TypeError('Bond expected')


class DynamicBond:
__slots__ = ('__order', '__p_order')
Expand Down Expand Up @@ -83,7 +94,7 @@ def __int__(self):
return hash(self)

def __hash__(self):
return (self.__order or 0) * 10 + (self.__p_order or 0)
return tuple_hash((self.__order or 0, self.__p_order or 0))

@property
def order(self) -> Optional[int]:
Expand All @@ -98,3 +109,79 @@ def copy(self) -> 'DynamicBond':
copy._DynamicBond__order = self.__order
copy._DynamicBond__p_order = self.__p_order
return copy

@classmethod
def from_bond(cls, bond):
if isinstance(bond, Bond):
copy = object.__new__(cls)
copy._DynamicBond__order = copy._DynamicBond__p_order = bond._Bond__order
return copy
elif isinstance(bond, cls):
copy = object.__new__(cls)
copy._DynamicBond__order = bond._DynamicBond__order
copy._DynamicBond__p_order = bond._DynamicBond__p_order
return copy
raise TypeError('DynamicBond expected')


class QueryBond:
__slots__ = ('__order',)

def __init__(self, order):
if isinstance(order, (list, tuple, set)):
if not all(isinstance(x, int) for x in order):
raise TypeError('invalid order value')
if any(x not in (1, 4, 2, 3, 8) for x in order):
raise ValueError('order should be from [1, 2, 3, 4, 8]')
order = tuple(sorted(set(order)))
elif isinstance(order, int):
if order not in (1, 4, 2, 3, 8):
raise ValueError('order should be from [1, 2, 3, 4, 8]')
order = (order,)
else:
raise TypeError('invalid order value')
self.__order = order

def __eq__(self, other):
if isinstance(other, Bond):
return other.order in self.__order
elif isinstance(other, QueryBond):
return self.__order == other.order
elif isinstance(other, int):
return other in self.__order
return False

def __repr__(self):
return f'{self.__class__.__name__}({self.__order})'

def __int__(self):
if len(self.__order) == 1:
return self.__order[0]
return tuple_hash(self.__order)

def __hash__(self):
return tuple_hash(self.__order)

@property
def order(self) -> int:
return self.__order

def copy(self) -> 'QueryBond':
copy = object.__new__(self.__class__)
copy._QueryBond__order = self.__order
return copy

@classmethod
def from_bond(cls, bond):
if isinstance(bond, Bond):
copy = object.__new__(cls)
copy._QueryBond__order = (bond._Bond__order,)
return copy
elif isinstance(bond, cls):
copy = object.__new__(cls)
copy._QueryBond__order = bond._QueryBond__order
return copy
raise TypeError('QueryBond or Bond expected')


__all__ = ['Bond', 'DynamicBond', 'QueryBond']
70 changes: 17 additions & 53 deletions CGRtools/containers/cgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,92 +174,56 @@ def substructure(self, atoms, *, as_query: bool = False, **kwargs) -> Union['CGR
:param meta: if True metadata will be copied to substructure
:param as_query: return Query object based on graph substructure
"""
sub, atoms = super().substructure(atoms, query.QueryCGRContainer if as_query else self.__class__, **kwargs)
sa = self._atoms
sub, atoms = super().substructure(atoms, graph_type=query.QueryCGRContainer if as_query else self.__class__,
atom_type=DynamicQueryElement if as_query else DynamicElement,
bond_type=DynamicBond, **kwargs)
spc = self._p_charges
spr = self._p_radicals

sub._p_charges = {n: spc[n] for n in atoms}
sub._p_radicals = {n: spr[n] for n in atoms}

if as_query:
sub._atoms = ca = {}
for n in atoms:
atom = sa[n]
atom = DynamicQueryElement.from_atomic_number(atom.atomic_number)(atom.isotope)
ca[n] = atom
atom._attach_to_graph(sub, n)

sb = self._bonds
sh = self._hybridizations
sph = self._p_hybridizations
sub._neighbors = {n: (sum(x.order is not None for x in sb[n].values()),) for n in atoms}
ngb = self.neighbors

sub._hybridizations = {n: (sh[n],) for n in atoms}
sub._p_neighbors = {n: (sum(x.p_order is not None for x in sb[n].values()),) for n in atoms}
sub._p_hybridizations = {n: (sph[n],) for n in atoms}

sub._neighbors = cn = {}
sub._p_neighbors = cpn = {}
for n in atoms:
sn, pn = ngb(n)
cn[n] = (sn,)
cpn[n] = (pn,)
else:
sub._conformers = [{n: c[n] for n in atoms} for c in self._conformers]
sub._atoms = ca = {}
for n in atoms:
atom = sa[n].copy()
ca[n] = atom
atom._attach_to_graph(sub, n)

# recalculate query marks
# recalculate query marks
sub._hybridizations = {}
sub._p_hybridizations = {}
for n in sub._atoms:
sub._calc_hybridization(n)
return sub

def union(self, other, **kwargs):
def union(self, other, **kwargs) -> 'CGRContainer':
if isinstance(other, CGRContainer):
u, other = super().union(other, **kwargs)
u, other = super().union(other, atom_type=DynamicElement, bond_type=DynamicBond, **kwargs)
u._conformers.clear()

u._p_charges.update(other._p_charges)
u._p_radicals.update(other._p_radicals)
u._hybridizations.update(other._hybridizations)
u._p_hybridizations.update(other._p_hybridizations)

ub = u._bonds
for n in other._bonds:
ub[n] = {}
seen = set()
for n, m_bond in other._bonds.items():
seen.add(n)
for m, bond in m_bond.items():
if m not in seen:
ub[n][m] = ub[m][n] = bond.copy()

ua = u._atoms
for n, atom in other._atoms.items():
atom = atom.copy()
ua[n] = atom
atom._attach_to_graph(u, n)
return u
elif isinstance(other, molecule.MoleculeContainer):
u, other = super().union(other, **kwargs)
u, other = super().union(other, atom_type=DynamicElement, bond_type=DynamicBond, **kwargs)
u._conformers.clear()
u._p_charges.update(other._charges)
u._p_radicals.update(other._radicals)
u._hybridizations.update(other._hybridizations)
u._p_hybridizations.update(other._hybridizations)

ub = u._bonds
for n, m_bond in other._bonds.items():
ub[n] = {m: DynamicBond(b.order, b.order) for m, b in m_bond.items()}

ua = u._atoms
for n, atom in other._atoms.items():
atom = DynamicElement.from_atomic_number(atom.atomic_number)(atom.isotope)
ua[n] = atom
atom._attach_to_graph(u, n)
return u
elif isinstance(other, Graph): # Query or CGRQuery
return other.union(self, **kwargs)
else:
raise TypeError('Graph expected')
raise TypeError('CGRContainer or MoleculeContainer expected')

def compose(self, other: Union['molecule.MoleculeContainer', 'CGRContainer']) -> 'CGRContainer':
"""
Expand Down

0 comments on commit 3c3701b

Please sign in to comment.