Skip to content

Commit

Permalink
!!Query update!!
Browse files Browse the repository at this point in the history
heteroatoms mark added.
  • Loading branch information
stsouko committed Dec 11, 2020
1 parent ddaf40b commit df58839
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 5 deletions.
7 changes: 7 additions & 0 deletions CGRtools/algorithms/smiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def __format__(self, format_spec):
!n - Disable neighbors marks in queries. Returns non-unique signature.
!g - Disable hydrogens marks in queries. Returns non-unique signature.
!c - Disable rings marks in queries. Returns non-unique signature.
!w - Disable heteroatoms marks in queries. Returns non-unique signature.
t - Use aromatic bonds instead aromatic atoms.
m - Set atom mapping.
r - Generate random-ordered smiles.
Expand All @@ -84,6 +85,8 @@ def __format__(self, format_spec):
kwargs['hydrogens'] = False
if '!c' in format_spec:
kwargs['rings'] = False
if '!w' in format_spec:
kwargs['heteroatoms'] = False
if 'r' in format_spec:
def w(x):
return random()
Expand Down Expand Up @@ -405,6 +408,7 @@ def _format_atom(self, n, **kwargs):
neighbors = self._neighbors[n]
hydrogens = self._hydrogens[n]
rings = self._rings_sizes[n]
has_heteroatoms = self._has_heteroatoms[n]

if atom.isotope:
smi = ['[', str(atom.isotope), atom.atomic_symbol]
Expand All @@ -430,6 +434,9 @@ def _format_atom(self, n, **kwargs):
smi.append(';Z')
smi.append(''.join(hybridization_str[x] for x in hybridization))

if kwargs.get('heteroatoms', True) and has_heteroatoms is not None:
smi.append(';W' if has_heteroatoms else ';!W')

if self._radicals[n]:
smi.append(';*')

Expand Down
8 changes: 8 additions & 0 deletions CGRtools/containers/molecule.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ def neighbors(self, n: int) -> int:
"""number of neighbors atoms excluding any-bonded"""
return sum(b.order != 8 for b in self._bonds[n].values())

@cached_args_method
def has_heteroatoms(self, n: int):
"""
True if atom has neighbored heteroatoms (not carbon or hydrogen)
"""
atoms = self._atoms
return any(atoms[m].atomic_number not in (1, 6) for m in self._bonds[n])

def remap(self, mapping, *, copy=False) -> 'MoleculeContainer':
h = super().remap(mapping, copy=copy)
mg = mapping.get
Expand Down
23 changes: 20 additions & 3 deletions CGRtools/containers/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>.
#
from typing import List, Tuple, Union, Dict
from typing import List, Tuple, Union, Dict, Optional
from . import molecule # cyclic imports resolve
from .bonds import Bond, QueryBond
from .common import Graph
Expand All @@ -30,7 +30,7 @@

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

def __init__(self):
self._neighbors: Dict[int, Tuple[int, ...]] = {}
Expand All @@ -40,6 +40,7 @@ def __init__(self):
self._cis_trans_stereo: Dict[Tuple[int, int], bool] = {}
self._hydrogens: Dict[int, Tuple[int, ...]] = {}
self._rings_sizes: Dict[int, Tuple[int, ...]] = {}
self._has_heteroatoms: Dict[int, Optional[bool]] = {}

super().__init__()

Expand All @@ -48,11 +49,14 @@ def add_atom(self, atom: Union[QueryElement, AnyElement, Element, int, str], *ar
hybridization: Union[int, List[int], Tuple[int, ...], None] = None,
hydrogens: Union[int, List[int], Tuple[int, ...], None] = None,
rings_sizes: Union[int, List[int], Tuple[int, ...], None] = None,
has_heteroatoms: Optional[bool] = None,
**kwargs):
neighbors = self._validate_neighbors(neighbors)
hybridization = self._validate_hybridization(hybridization)
hydrogens = self._validate_neighbors(hydrogens)
rings_sizes = self._validate_rings(rings_sizes)
if has_heteroatoms is not None and not isinstance(has_heteroatoms, bool):
raise TypeError('has_heteroatoms should be None or bool')

if not isinstance(atom, (QueryElement, AnyElement)):
if isinstance(atom, Element):
Expand All @@ -69,6 +73,7 @@ def add_atom(self, atom: Union[QueryElement, AnyElement, Element, int, str], *ar
self._hybridizations[_map] = hybridization
self._hydrogens[_map] = hydrogens
self._rings_sizes[_map] = rings_sizes
self._has_heteroatoms[_map] = has_heteroatoms
return _map

def add_bond(self, n, m, bond: Union[QueryBond, Bond, int, Tuple[int, ...]]):
Expand Down Expand Up @@ -106,6 +111,7 @@ def delete_atom(self, n):
del self._hybridizations[n]
del self._hydrogens[n]
del self._rings_sizes[n]
del self._has_heteroatoms[n]

sas = self._atoms_stereo
if n in sas:
Expand Down Expand Up @@ -147,6 +153,7 @@ def remap(self, mapping, *, copy=False) -> 'QueryContainer':
sn = self._neighbors
shg = self._hydrogens
srs = self._rings_sizes
sha = self._has_heteroatoms

if copy:
hn = h._neighbors
Expand All @@ -156,6 +163,7 @@ def remap(self, mapping, *, copy=False) -> 'QueryContainer':
hcs = h._cis_trans_stereo
hhg = h._hydrogens
hrs = h._rings_sizes
hha = h._has_heteroatoms
else:
hn = {}
hh = {}
Expand All @@ -164,13 +172,15 @@ def remap(self, mapping, *, copy=False) -> 'QueryContainer':
hcs = {}
hhg = {}
hrs = {}
hha = {}

for n, hyb in self._hybridizations.items():
m = mg(n, n)
hn[m] = sn[n]
hh[m] = hyb
hhg[m] = shg[n]
hrs[m] = srs[n]
hha[m] = sha[n]

for n, stereo in self._atoms_stereo.items():
has[mg(n, n)] = stereo
Expand All @@ -196,6 +206,7 @@ def copy(self, **kwargs) -> 'QueryContainer':
copy._neighbors = self._neighbors.copy()
copy._hybridizations = self._hybridizations.copy()
copy._hydrogens = self._hydrogens.copy()
copy._has_heteroatoms = self._has_heteroatoms.copy()
copy._rings_sizes = self._rings_sizes.copy()
copy._atoms_stereo = self._atoms_stereo.copy()
copy._allenes_stereo = self._allenes_stereo.copy()
Expand All @@ -217,11 +228,13 @@ def substructure(self, atoms, **kwargs) -> 'QueryContainer':
sh = self._hybridizations
shg = self._hydrogens
srs = self._rings_sizes
sha = self._has_heteroatoms

sub._neighbors = {n: sn[n] for n in atoms}
sub._hybridizations = {n: sh[n] for n in atoms}
sub._hydrogens = {n: shg[n] for n in atoms}
sub._rings_sizes = {n: srs[n] for n in atoms}
sub._has_heteroatoms = {n: sha[n] for n in atoms}

lost = {n for n, a in sa.items() if a.atomic_number != 1} - set(atoms) # atoms not in substructure
not_skin = {n for n in atoms if lost.isdisjoint(sb[n])}
Expand All @@ -244,6 +257,7 @@ def union(self, other, **kwargs) -> 'QueryContainer':
u._atoms_stereo.update(other._atoms_stereo)
u._allenes_stereo.update(other._allenes_stereo)
u._cis_trans_stereo.update(other._cis_trans_stereo)
u._has_heteroatoms.update(other._has_heteroatoms)
return u
else:
raise TypeError('QueryContainer expected')
Expand Down Expand Up @@ -322,7 +336,7 @@ def __getstate__(self):
return {'atoms_stereo': self._atoms_stereo, 'allenes_stereo': self._allenes_stereo,
'cis_trans_stereo': self._cis_trans_stereo, 'neighbors': self._neighbors,
'hybridizations': self._hybridizations, 'hydrogens': self._hydrogens,
'rings_sizes': self._rings_sizes, **super().__getstate__()}
'rings_sizes': self._rings_sizes, 'has_heteroatoms': self._has_heteroatoms, **super().__getstate__()}

def __setstate__(self, state):
if 'node' in state: # 3.1 compatibility.
Expand Down Expand Up @@ -361,6 +375,8 @@ def __setstate__(self, state):
b._QueryBond__order = bond._Bond__order
bn[m] = b
state['bonds'] = bonds
if 'has_heteroatoms' not in state: # <4.1.4
state['has_heteroatoms'] = {n: None for n in state['atoms']}

super().__setstate__(state)
self._atoms_stereo = state['atoms_stereo']
Expand All @@ -370,6 +386,7 @@ def __setstate__(self, state):
self._hybridizations = state['hybridizations']
self._hydrogens = state['hydrogens']
self._rings_sizes = state['rings_sizes']
self._has_heteroatoms = state['has_heteroatoms']


__all__ = ['QueryContainer']
7 changes: 7 additions & 0 deletions CGRtools/periodictable/element/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ def total_hydrogens(self) -> int:
except AttributeError:
raise IsNotConnectedAtom

@property
def has_heteroatoms(self) -> int:
try:
return self._graph().has_heteroatoms(self._map)
except AttributeError:
raise IsNotConnectedAtom

@property
def neighbors(self) -> int:
try:
Expand Down
26 changes: 25 additions & 1 deletion CGRtools/periodictable/element/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>.
#
from typing import Tuple, Dict, Type, List
from typing import Tuple, Dict, Type, List, Optional
from .core import Core
from .element import Element
from ..._functions import tuple_hash
Expand Down Expand Up @@ -51,6 +51,24 @@ def hybridization(self, hybridization):
except AttributeError:
raise IsNotConnectedAtom

@property
def has_heteroatoms(self) -> Optional[bool]:
try:
return self._graph()._has_heteroatoms[self._map]
except AttributeError:
raise IsNotConnectedAtom

@has_heteroatoms.setter
def has_heteroatoms(self, mark: Optional[bool]):
if mark is not None and not isinstance(mark, bool):
raise TypeError('bool expected')
try:
g = self._graph()
g._has_heteroatoms[self._map] = mark
g.flush_cache()
except AttributeError:
raise IsNotConnectedAtom

@property
def in_ring(self) -> bool:
"""
Expand Down Expand Up @@ -189,6 +207,8 @@ def __eq__(self, other):
return False
if self.implicit_hydrogens and other.implicit_hydrogens not in self.implicit_hydrogens:
return False
if self.has_heteroatoms is not None and other.has_heteroatoms != self.has_heteroatoms:
return False
return True
elif isinstance(other, QueryElement) and self.atomic_number == other.atomic_number and \
self.isotope == other.isotope and self.charge == other.charge and self.is_radical == other.is_radical \
Expand Down Expand Up @@ -261,6 +281,8 @@ def __eq__(self, other):
return False
if self.implicit_hydrogens and other.implicit_hydrogens not in self.implicit_hydrogens:
return False
if self.has_heteroatoms is not None and other.has_heteroatoms != self.has_heteroatoms:
return False
return True
elif isinstance(other, Query) and self.charge == other.charge and self.is_radical == other.is_radical \
and self.neighbors == other.neighbors and self.hybridization == other.hybridization \
Expand Down Expand Up @@ -303,6 +325,8 @@ def __eq__(self, other):
return False
if self.implicit_hydrogens and other.implicit_hydrogens not in self.implicit_hydrogens:
return False
if self.has_heteroatoms is not None and other.has_heteroatoms != self.has_heteroatoms:
return False
return True
elif isinstance(other, Query) and self.charge == other.charge and self.is_radical == other.is_radical \
and self.neighbors == other.neighbors and self.hybridization == other.hybridization \
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def finalize_options(self):

setup(
name='CGRtools',
version='4.1.3',
version='4.1.4',
packages=['CGRtools', 'CGRtools.algorithms', 'CGRtools.algorithms.components', 'CGRtools.containers',
'CGRtools.files', 'CGRtools.files._mdl', 'CGRtools.periodictable', 'CGRtools.periodictable.element',
'CGRtools.utils', 'CGRtools.attributes'],
Expand Down

0 comments on commit df58839

Please sign in to comment.