Skip to content

Commit

Permalink
new aromatize algorithm.
Browse files Browse the repository at this point in the history
refactored helper function.
  • Loading branch information
stsouko committed Dec 4, 2020
1 parent d415734 commit b6a19f8
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 41 deletions.
36 changes: 35 additions & 1 deletion CGRtools/_functions.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 itertools import product
from sys import version_info


# lazy itertools.product with diagonal combination precedence
Expand Down Expand Up @@ -66,4 +67,37 @@ def lazy_product(*args):
yield tuple(p[x] for x, p in zip(ind, pools))


__all__ = ['lazy_product']
if version_info[1] >= 8:
tuple_hash = hash
else:
def tuple_hash(v):
"""
Python 3.8 hash for tuples implemented on python.
"""
acc = 0x27D4EB2F165667C5
for el in v:
if isinstance(el, tuple):
lane = tuple_hash(el)
else:
lane = hash(el)
if lane == -1:
return -1
elif lane < 0:
lane += 0x10000000000000000 # to unsigned

acc += lane * 0xC2B2AE3D27D4EB4F
acc %= 0x10000000000000000
acc = (acc << 31) % 0x10000000000000000 | (acc >> 33)
acc *= 0x9E3779B185EBCA87

acc += len(v) ^ 2870177450013471926 # 0xC2B2AE3D27D4EB4F ^ 3527539
acc %= 0x10000000000000000

if acc == 0xFFFFFFFFFFFFFFFF:
return 1546275796
elif acc > 0x7FFFFFFFFFFFFFFF: # (1 << 63) - 1 = largest positive number
return acc - 0x10000000000000000
return acc


__all__ = ['lazy_product', 'tuple_hash']
50 changes: 50 additions & 0 deletions CGRtools/algorithms/aromatics.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
# 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 CachedMethods import cached_property
from collections import defaultdict, deque
from typing import List, Optional, Tuple
from .._functions import lazy_product
from ..containers import query # cyclic imports resolve
from ..exceptions import InvalidAromaticRing


Expand All @@ -44,6 +46,7 @@ def thiele(self, *, fix_tautomers=True) -> bool:
pyroles = set()
acceptors = set()
donors = []
freaks = []
for ring in self.sssr:
lr = len(ring)
if not 3 < lr < 8: # skip 3-membered and big rings
Expand Down Expand Up @@ -78,6 +81,9 @@ def thiele(self, *, fix_tautomers=True) -> bool:
for n, m in zip(ring, ring[1:]):
rings[n].add(m)
rings[m].add(n)
# like N1C=Cn2cccc12
elif lr == 5 and sum(atoms[x].atomic_number == 7 and not charges[x] for x in ring) > 1:
freaks.append(ring)
if not rings:
return False
double_bonded = {n for n in rings if any(m not in rings and b.order == 2 for m, b in bonds[n].items())}
Expand Down Expand Up @@ -166,6 +172,18 @@ def thiele(self, *, fix_tautomers=True) -> bool:
bonds[n][m]._Bond__order = 4

self.flush_cache()
for ring in freaks: # aromatize rule based
rs = set(ring)
for q in self.__freaks:
components, closures = q._compiled_query
if any(q._get_mapping(components[0], closures, atoms, bonds, rs, self.atoms_order)):
n, *_, m = ring
bonds[n][m]._Bond__order = 4
for n, m in zip(ring, ring[1:]):
bonds[n][m]._Bond__order = 4
for n in ring:
sh[n] = 4

self._fix_stereo() # check if any stereo centers vanished.
return True

Expand Down Expand Up @@ -530,5 +548,37 @@ def __kekule_component(rings, double_bonded, pyroles):
if nether_yielded:
raise InvalidAromaticRing(f'kekule form not found for: {list(rings)}')

@cached_property
def __freaks(self):
rules = []

q = query.QueryContainer()
q.add_atom('N', neighbors=2)
q.add_atom('A')
q.add_atom('A')
q.add_atom('A')
q.add_atom('A')
q.add_bond(1, 2, 1)
q.add_bond(2, 3, 2)
q.add_bond(3, 4, 1)
q.add_bond(4, 5, 4)
q.add_bond(1, 5, 1)
rules.append(q)

q = query.QueryContainer()
q.add_atom('N', neighbors=2)
q.add_atom('A')
q.add_atom('A')
q.add_atom('A')
q.add_atom('A')
q.add_bond(1, 2, 1)
q.add_bond(2, 3, 4)
q.add_bond(3, 4, 1)
q.add_bond(4, 5, 4)
q.add_bond(1, 5, 1)
rules.append(q)

return rules


__all__ = ['Aromatize']
38 changes: 2 additions & 36 deletions CGRtools/algorithms/morgan.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,8 @@
from itertools import groupby
from logging import warning
from operator import itemgetter
from sys import version_info
from typing import Dict


if version_info[1] >= 8:
tuple_hash = hash
else:
def tuple_hash(v):
"""
Python 3.8 hash for tuples implemented on python.
Working only for nonnested tuples.
"""
acc = 0x27D4EB2F165667C5
for el in v:
if isinstance(el, tuple):
lane = tuple_hash(el)
else:
lane = hash(el)
if lane == -1:
return -1
elif lane < 0:
lane += 0x10000000000000000 # to unsigned

acc += lane * 0xC2B2AE3D27D4EB4F
acc %= 0x10000000000000000
acc = (acc << 31) % 0x10000000000000000 | (acc >> 33)
acc *= 0x9E3779B185EBCA87

acc += len(v) ^ 2870177450013471926 # 0xC2B2AE3D27D4EB4F ^ 3527539
acc %= 0x10000000000000000

if acc == 0xFFFFFFFFFFFFFFFF:
return 1546275796
elif acc > 0x7FFFFFFFFFFFFFFF: # (1 << 63) - 1 = largest positive number
return acc - 0x10000000000000000
return acc
from .._functions import tuple_hash


class Morgan:
Expand Down Expand Up @@ -108,4 +74,4 @@ def _morgan(self, weights: Dict[int, int]) -> Dict[int, int]:
start=1) for n, _ in g}


__all__ = ['Morgan', 'tuple_hash']
__all__ = ['Morgan']
2 changes: 1 addition & 1 deletion CGRtools/periodictable/element/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#
from typing import Type
from .core import Core
from ...algorithms.morgan import tuple_hash
from ..._functions import tuple_hash
from ...exceptions import IsNotConnectedAtom


Expand Down
2 changes: 1 addition & 1 deletion CGRtools/periodictable/element/dynamic_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from typing import Tuple, Dict, Type
from .core import Core
from .dynamic import Dynamic, DynamicElement
from ...algorithms.morgan import tuple_hash
from ..._functions import tuple_hash
from ...exceptions import IsNotConnectedAtom


Expand Down
2 changes: 1 addition & 1 deletion CGRtools/periodictable/element/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from collections import defaultdict
from typing import Optional, Tuple, Dict, Set, List, Type
from .core import Core
from ...algorithms.morgan import tuple_hash
from ..._functions import tuple_hash
from ...exceptions import IsNotConnectedAtom, ValenceError


Expand Down
2 changes: 1 addition & 1 deletion CGRtools/periodictable/element/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from typing import Tuple, Dict, Type, List
from .core import Core
from .element import Element
from ...algorithms.morgan import tuple_hash
from ..._functions import tuple_hash
from ...exceptions import IsNotConnectedAtom


Expand Down

0 comments on commit b6a19f8

Please sign in to comment.