Repository navigation
Expand file tree
/
Copy pathmorgan.py
More file actions
82 lines (67 loc) · 2.81 KB
/
Copy pathmorgan.py
File metadata and controls
82 lines (67 loc) · 2.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# -*- coding: utf-8 -*-
#
# Copyright 2017-2024 Ramil Nugmanov <nougmanoff@protonmail.com>
# This file is part of chython.
#
# chython is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# 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 functools import cached_property
from itertools import groupby
from logging import getLogger
from operator import itemgetter
from typing import Dict, TYPE_CHECKING
logger = getLogger('chython.morgan')
if TYPE_CHECKING:
from chython.containers import MoleculeContainer
class Morgan:
__slots__ = ()
@cached_property
def atoms_order(self: 'MoleculeContainer') -> Dict[int, int]:
"""
Morgan like algorithm for graph nodes ordering
:return: dict of atom-order pairs
"""
if not self: # for empty containers
return {}
elif len(self) == 1: # optimize single atom containers
return dict.fromkeys(self, 1)
return _morgan({n: hash(a) for n, a in self.atoms()}, self.int_adjacency)
@cached_property
def int_adjacency(self: 'MoleculeContainer') -> Dict[int, Dict[int, int]]:
"""
Adjacency with integer-coded bonds.
"""
return {n: {m: hash(b) for m, b in mb.items()} for n, mb in self._bonds.items()}
def _morgan(atoms: Dict[int, int], bonds: Dict[int, Dict[int, int]]) -> Dict[int, int]:
tries = len(atoms) - 1
numb = len(set(atoms.values()))
stab = old_numb = 0
for _ in range(tries):
atoms = {n: hash((atoms[n], *(x for x in sorted((atoms[m], b) for m, b in ms.items()) for x in x)))
for n, ms in bonds.items()}
old_numb, numb = numb, len(set(atoms.values()))
if numb == len(atoms): # each atom now unique
break
elif numb == old_numb: # not changed. molecules like benzene
if stab == 3:
break
stab += 1
elif stab: # changed unique atoms number. reset stability check.
stab = 0
else:
if numb < old_numb:
logger.warning('number of attempts exceeded. uniqueness has decreased.')
return {n: i for i, (_, g) in enumerate(groupby(sorted(atoms.items(), key=itemgetter(1)), key=itemgetter(1)),
start=1) for n, _ in g}
__all__ = ['Morgan']