rdkit.Chem.FeatMaps.FeatMapUtils

Package rdkit :: Package Chem :: Package FeatMaps :: Module FeatMapUtils |
[hide private]
[frames] | no frames]

# Source Code for Module rdkit.Chem.FeatMaps.FeatMapUtils

      1  # $Id$
      2  #
      3  # Copyright (C) 2006 Greg Landrum
      4  #
      5  #   @@ All Rights Reserved @@
      6  #  This file is part of the RDKit.
      7  #  The contents are covered by the terms of the BSD license
      8  #  which is included in the file license.txt, found at the root
      9  #  of the RDKit source tree.
     10  #
     11  import copy
     12  from rdkit.Chem.FeatMaps import FeatMaps
     13
     14
     15 -class MergeMethod(object):
     16    # Put the new point at the weighted average position of the two fused points
     17    WeightedAverage = 0
     18    # Put the new point at the un-weighted average position of the two fused points
     19    Average = 1
     20    # Put the new point at the position of the larger (by weight) of the two points
     21    UseLarger = 2
     22
     23    @classmethod
     24 -  def valid(cls, mergeMethod):
     25      """ Check that mergeMethod is valid """
     26      if mergeMethod not in (cls.WeightedAverage, cls.Average, cls.UseLarger):
     27        raise ValueError('unrecognized mergeMethod')
     28
     29
     30 -class MergeMetric(object):
     31    # Do not merge points
     32    NoMerge = 0
     33    # merge two points if they come within a threshold distance
     34    Distance = 1
     35    # merge two points if their percent overlap exceeds a threshold
     36    Overlap = 2
     37
     38    @classmethod
     39 -  def valid(cls, mergeMetric):
     40      """ Check that mergeMetric is valid """
     41      if mergeMetric not in (cls.NoMerge, cls.Distance, cls.Overlap):
     42        raise ValueError('unrecognized mergeMetric')
     43
     44
     45 -class DirMergeMode(object):
     46    # Do not merge directions (i.e. keep all direction vectors)
     47    NoMerge = 0
     48    # Sum direction vectors
     49    Sum = 1
     50
     51    @classmethod
     52 -  def valid(cls, dirMergeMode):
     53      """ Check that dirMergeMode is valid """
     54      if dirMergeMode not in (cls.NoMerge, cls.Sum):
     55        raise ValueError('unrecognized dirMergeMode')
     56
     57
     58 -def __copyAll(res, fm1, fm2):
     59    """ no user-serviceable parts inside """
     60    for feat in fm1.GetFeatures():
     61      res.AddFeatPoint(copy.deepcopy(feat))
     62    for feat in fm2.GetFeatures():
     63      res.AddFeatPoint(copy.deepcopy(feat))
     64
     65
     66 -def GetFeatFeatDistMatrix(fm, mergeMetric, mergeTol, dirMergeMode, compatFunc):
     67    """
     68
     69      NOTE that mergeTol is a max value for merging when using distance-based
     70      merging and a min value when using score-based merging.
     71
     72    """
     73    MergeMetric.valid(mergeMetric)
     74
     75    dists = [[1e8] * fm.GetNumFeatures() for _ in range(fm.GetNumFeatures())]
     76    if mergeMetric == MergeMetric.NoMerge:
     77      return dists
     78    elif mergeMetric == MergeMetric.Distance:
     79      mergeTol2 = mergeTol * mergeTol
     80      for i in range(fm.GetNumFeatures()):
     81        ptI = fm.GetFeature(i)
     82        for j in range(i + 1, fm.GetNumFeatures()):
     83          ptJ = fm.GetFeature(j)
     84          if compatFunc(ptI, ptJ):
     85            dist2 = ptI.GetDist2(ptJ)
     86            if dist2 < mergeTol2:
     87              dists[i][j] = dist2
     88              dists[j][i] = dist2
     89    elif mergeMetric == MergeMetric.Overlap:
     90      for i in range(fm.GetNumFeatures()):
     91        ptI = fm.GetFeature(i)
     92        for j in range(i + 1, fm.GetNumFeatures()):
     93          ptJ = fm.GetFeature(j)
     94          if compatFunc(ptI, ptJ):
     95            score = fm.GetFeatFeatScore(ptI, ptJ, typeMatch=False)
     96            score *= -1 * ptJ.weight
     97            if score < mergeTol:
     98              dists[i][j] = score
     99              dists[j][i] = score
    100    return dists
    101
    102
    103 -def familiesMatch(f1, f2):
    104    return f1.GetFamily() == f2.GetFamily()
    105
    106
    107 -def feq(v1, v2, tol=1e-4):
    108    return abs(v1 - v2) < tol
    109
    110
    111 -def MergeFeatPoints(fm, mergeMetric=MergeMetric.NoMerge, mergeTol=1.5,
    112                      dirMergeMode=DirMergeMode.NoMerge, mergeMethod=MergeMethod.WeightedAverage,
    113                      compatFunc=familiesMatch):
    114    """
    115
    116      NOTE that mergeTol is a max value for merging when using distance-based
    117      merging and a min value when using score-based merging.
    118
    119      returns whether or not any points were actually merged
    120
    121    """
    122    MergeMetric.valid(mergeMetric)
    123    MergeMethod.valid(mergeMethod)
    124    DirMergeMode.valid(dirMergeMode)
    125
    126    res = False
    127    if mergeMetric == MergeMetric.NoMerge:
    128      return res
    129    dists = GetFeatFeatDistMatrix(fm, mergeMetric, mergeTol, dirMergeMode, compatFunc)
    130    distOrders = [None] * len(dists)
    131    for i in range(len(dists)):
    132      distV = dists[i]
    133      distOrders[i] = []
    134      for j, dist in enumerate(distV):
    135        if dist < mergeTol:
    136          distOrders[i].append((dist, j))
    137      distOrders[i].sort()
    138
    139    # print('distOrders:')
    140    # print(distOrders)
    141
    142    # we now know the "distances" and have rank-ordered list of
    143    # each point's neighbors. Work with that.
    144
    145    # progressively merge nearest neighbors until there
    146    # are no more points left to merge
    147    featsInPlay = list(range(fm.GetNumFeatures()))
    148    featsToRemove = []
    149    # print '--------------------------------'
    150    while featsInPlay:
    151      # find two features who are mutual nearest neighbors:
    152      fipCopy = featsInPlay[:]
    153      for fi in fipCopy:
    154        # print('>>>',fi,fipCopy,featsInPlay)
    155        # print('\t',distOrders[fi])
    156        mergeThem = False
    157        if not distOrders[fi]:
    158          featsInPlay.remove(fi)
    159          continue
    160        dist, nbr = distOrders[fi][0]
    161        if nbr not in featsInPlay:
    162          continue
    163        if distOrders[nbr][0][1] == fi:
    164          # print 'direct:',fi,nbr
    165          mergeThem = True
    166        else:
    167          # it may be that there are several points at about the same distance,
    168          # check for that now
    169          if (feq(distOrders[nbr][0][0], dist)):
    170            for distJ, nbrJ in distOrders[nbr][1:]:
    171              if feq(dist, distJ):
    172                if nbrJ == fi:
    173                  # print 'indirect: ',fi,nbr
    174                  mergeThem = True
    175                  break
    176              else:
    177                break
    178        # print '    bottom:',mergeThem
    179        if mergeThem:
    180          break
    181      if mergeThem:
    182        res = True
    183        featI = fm.GetFeature(fi)
    184        nbrFeat = fm.GetFeature(nbr)
    185
    186        if mergeMethod == MergeMethod.WeightedAverage:
    187          newPos = featI.GetPos() * featI.weight + nbrFeat.GetPos() * nbrFeat.weight
    188          newPos /= (featI.weight + nbrFeat.weight)
    189          newWeight = (featI.weight + nbrFeat.weight) / 2
    190        elif mergeMethod == MergeMethod.Average:
    191          newPos = featI.GetPos() + nbrFeat.GetPos()
    192          newPos /= 2
    193          newWeight = (featI.weight + nbrFeat.weight) / 2
    194        elif mergeMethod == MergeMethod.UseLarger:
    195          if featI.weight > nbrFeat.weight:
    196            newPos = featI.GetPos()
    197            newWeight = featI.weight
    198          else:
    199            newPos = nbrFeat.GetPos()
    200            newWeight = nbrFeat.weight
    201
    202        featI.SetPos(newPos)
    203        featI.weight = newWeight
    204
    205        # nbr and fi are no longer valid targets:
    206        # print 'nbr done:',nbr,featsToRemove,featsInPlay
    207        featsToRemove.append(nbr)
    208        featsInPlay.remove(fi)
    209        featsInPlay.remove(nbr)
    210        for nbrList in distOrders:
    211          try:
    212            nbrList.remove(fi)
    213          except ValueError:
    214            pass
    215          try:
    216            nbrList.remove(nbr)
    217          except ValueError:
    218            pass
    219      else:
    220        # print ">>>> Nothing found, abort"
    221        break
    222    featsToRemove.sort()
    223    for i, fIdx in enumerate(featsToRemove):
    224      fm.DropFeature(fIdx - i)
    225    return res
    226
    227
    228 -def CombineFeatMaps(fm1, fm2, mergeMetric=MergeMetric.NoMerge, mergeTol=1.5,
    229                      dirMergeMode=DirMergeMode.NoMerge):
    230    """
    231       the parameters will be taken from fm1
    232    """
    233    res = FeatMaps.FeatMap(params=fm1.params)
    234
    235    __copyAll(res, fm1, fm2)
    236    if mergeMetric != MergeMetric.NoMerge:
    237      MergeFeatPoints(res, mergeMetric=mergeMetric, mergeTol=mergeTol)
    238    return res
    239