Skip to content

Commit 24f0007

Browse files
authored
refactor: improve readability and maintainability of AAP similarity code (rdkit#9277)
* refactor: clean up AAP similarity logic and add type hints * Refactor AAP similarity implementation for clarity and maintainability * Refactor AAP similarity implementation for clarity and maintainability * ran yapf over the code
1 parent 2f6bbe0 commit 24f0007

1 file changed

Lines changed: 37 additions & 45 deletions

File tree

‎Contrib/AtomAtomSimilarity/AtomAtomPathSimilarity.py‎

Lines changed: 37 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,20 @@
99
import numpy
1010
from scipy.optimize import linear_sum_assignment
1111

12-
from rdkit import Chem, DataStructs
13-
from rdkit.Chem import AllChem, rdmolops
14-
from rdkit.Chem.Fingerprints import FingerprintMols
12+
from rdkit import Chem
13+
from rdkit.Chem import rdmolops
1514

16-
_BK_ = {
15+
_bondTypeCodes = {
1716
Chem.rdchem.BondType.SINGLE: 1,
1817
Chem.rdchem.BondType.DOUBLE: 2,
1918
Chem.rdchem.BondType.TRIPLE: 3,
2019
Chem.rdchem.BondType.AROMATIC: 4
2120
}
2221
_BONDSYMBOL_ = {1: '-', 2: '=', 3: '#', 4: ':'}
2322

24-
#_nAT_ = 217 # 108*2+1
25-
_nAT_ = 223 # Gobbi code actually uses the first prime higher than 217, not 217 itself
26-
_nBT_ = 5
23+
#_atomHashModulus = 217 # 108*2+1
24+
_atomHashModulus = 223 # Gobbi code actually uses the first prime higher than 217, not 217 itself
25+
_bondHashModulus = 5
2726

2827
#def FindAllPathsOfLengthN_Gobbi(mol, length, rootedAtAtom=-1, uniquepaths=True):
2928
# return FindAllPathsOfLengthMToN(mol, length, length, rootedAtAtom=rootedAtAtom, uniquepaths=uniquepaths)
@@ -61,12 +60,12 @@ def _FindAllPathsOfLengthMToN_Gobbi(atom, path, minlength, maxlength, visited, p
6160
if len(path) >= minlength and len(path) <= maxlength:
6261
paths.append(tuple(path))
6362
if len(path) < maxlength:
64-
a1 = bond.GetBeginAtom()
65-
a2 = bond.GetEndAtom()
66-
if a1.GetIdx() == atom.GetIdx():
67-
nextatom = a2
63+
beginAtom = bond.GetBeginAtom()
64+
endAtom = bond.GetEndAtom()
65+
if beginAtom.GetIdx() == atom.GetIdx():
66+
nextatom = endAtom
6867
else:
69-
nextatom = a1
68+
nextatom = beginAtom
7069
nextatomidx = nextatom.GetIdx()
7170
if nextatomidx not in visited:
7271
visited.add(nextatomidx)
@@ -79,7 +78,7 @@ def getpathintegers(m1, uptolength=7):
7978
'''returns a list of integers describing the paths for molecule m1. This uses numpy 16 bit unsigned integers to reproduce the data in the Gobbi paper. The returned list is sorted'''
8079
bondtypelookup = {}
8180
for b in m1.GetBonds():
82-
bondtypelookup[b.GetIdx()] = _BK_[b.GetBondType()], b.GetBeginAtom(), b.GetEndAtom()
81+
bondtypelookup[b.GetIdx()] = _bondTypeCodes[b.GetBondType()], b.GetBeginAtom(), b.GetEndAtom()
8382
pathintegers = {}
8483
for a in m1.GetAtoms():
8584
idx = a.GetIdx()
@@ -88,40 +87,32 @@ def getpathintegers(m1, uptolength=7):
8887
# for path in rdmolops.FindAllPathsOfLengthN(m1, pathlength, rootedAtAtom=idx):
8988
for ipath, path in enumerate(
9089
FindAllPathsOfLengthMToN_Gobbi(m1, 1, uptolength, rootedAtAtom=idx, uniquepaths=False)):
91-
strpath = []
9290
currentidx = idx
9391
res = []
9492
for ip, p in enumerate(path):
95-
bk, a1, a2 = bondtypelookup[p]
96-
strpath.append(_BONDSYMBOL_[bk])
97-
if a1.GetIdx() == currentidx:
98-
a = a2
93+
bondTypeCode, beginAtom, endAtom = bondtypelookup[p]
94+
if beginAtom.GetIdx() == currentidx:
95+
a = endAtom
9996
else:
100-
a = a1
101-
ak = a.GetAtomicNum()
97+
a = beginAtom
98+
neighborAtomCode = a.GetAtomicNum()
10299
if a.GetIsAromatic():
103-
ak += 108
100+
neighborAtomCode += 108
104101
#trying to get the same behaviour as the Gobbi test code - it looks like a circular path includes the bond, but not the closure atom - this fix works
105102
if a.GetIdx() == idx:
106-
ak = None
107-
if ak is not None:
108-
astr = a.GetSymbol()
109-
if a.GetIsAromatic():
110-
strpath.append(astr.lower())
111-
else:
112-
strpath.append(astr)
113-
res.append((bk, ak))
103+
neighborAtomCode = None
104+
res.append((bondTypeCode, neighborAtomCode))
114105
currentidx = a.GetIdx()
115106
pathuniqueint = numpy.ushort(0) # work with 16 bit unsigned integers and ignore overflow...
116-
for ires, (bi, ai) in enumerate(res):
107+
for ires, (bondValue, atomValue) in enumerate(res):
117108
#use 16 bit unsigned integer arithmetic to reproduce the Gobbi ints
118-
# pathuniqueint = ((pathuniqueint+bi)*_nAT_+ai)*_nBT_
119-
val1 = pathuniqueint + numpy.ushort(bi)
120-
val2 = val1 * numpy.ushort(_nAT_)
109+
# pathuniqueint = ((pathuniqueint+bondValue)*_atomHashModulus+atomValue)*_bondHashModulus
110+
val1 = pathuniqueint + numpy.ushort(bondValue)
111+
val2 = val1 * numpy.ushort(_atomHashModulus)
121112
#trying to get the same behaviour as the Gobbi test code - it looks like a circular path includes the bond, but not the closure atom - this fix works
122-
if ai is not None:
123-
val3 = val2 + numpy.ushort(ai)
124-
val4 = val3 * numpy.ushort(_nBT_)
113+
if atomValue is not None:
114+
val3 = val2 + numpy.ushort(atomValue)
115+
val4 = val3 * numpy.ushort(_bondHashModulus)
125116
else:
126117
val4 = val2
127118
pathuniqueint = val4
@@ -140,14 +131,14 @@ def getcommon(l1, ll1, l2, ll2):
140131
ix1 = 0
141132
ix2 = 0
142133
while (ix1 < ll1) and (ix2 < ll2):
143-
a1 = l1[ix1]
144-
a2 = l2[ix2]
145-
#a1 is < or > more often that ==
146-
if a1 < a2:
134+
beginAtom = l1[ix1]
135+
endAtom = l2[ix2]
136+
#beginAtom is < or > more often that ==
137+
if beginAtom < endAtom:
147138
ix1 += 1
148-
elif a1 > a2:
139+
elif beginAtom > endAtom:
149140
ix2 += 1
150-
else: # a1 == a2:
141+
else: # beginAtom == endAtom:
151142
ncommon += 1
152143
ix1 += 1
153144
ix2 += 1
@@ -206,19 +197,20 @@ def getsimab(mappings, simmatrixdict):
206197
def getsimmatrix(m1, m1pathintegers, m2, m2pathintegers):
207198
'''generate a matrix of atom atom similarities. See Figure 4'''
208199

209-
aidata = [((ai.GetAtomicNum(), ai.GetIsAromatic()), ai.GetIdx()) for ai in m1.GetAtoms()]
200+
aidata = [((atomValue.GetAtomicNum(), atomValue.GetIsAromatic()), atomValue.GetIdx())
201+
for atomValue in m1.GetAtoms()]
210202
bjdata = [((bj.GetAtomicNum(), bj.GetIsAromatic()), bj.GetIdx()) for bj in m2.GetAtoms()]
211203

212204
simmatrixarray = numpy.zeros((len(aidata), len(bjdata)))
213205

214-
for ai, (aitype, aiidx) in enumerate(aidata):
206+
for atomValue, (aitype, aiidx) in enumerate(aidata):
215207
aipaths = m1pathintegers[aiidx]
216208
naipaths = len(aipaths)
217209
for bj, (bjtype, bjidx) in enumerate(bjdata):
218210
if aitype == bjtype:
219211
bjpaths = m2pathintegers[bjidx]
220212
nbjpaths = len(bjpaths)
221-
simmatrixarray[ai][bj] = getsimaibj(aipaths, bjpaths, naipaths, nbjpaths)
213+
simmatrixarray[atomValue][bj] = getsimaibj(aipaths, bjpaths, naipaths, nbjpaths)
222214
return simmatrixarray
223215

224216

0 commit comments

Comments
 (0)