Example #1
0
 def __init__(self,
              distCutoff=distCutoff,
              distCutoff2=distCutoff2,
              d2min=d2min,
              d2max=d2max,
              d3min=d3min,
              d3max=d3max,
              a2min=a2min,
              a2max=a2max,
              a3min=a3min,
              a3max=a3max,
              donorTypes=allDonors,
              acceptorTypes=allAcceptors,
              distOnly=False):
     d = self.paramDict = {}
     d['distCutoff'] = distCutoff
     d['distCutoff2'] = distCutoff2
     d['d2min'] = d2min
     d['d2max'] = d2max
     d['d3min'] = d3min
     d['d3max'] = d3max
     d['a2min'] = a2min
     d['a2max'] = a2max
     d['a3min'] = a3min
     d['a3max'] = a3max
     d['donorTypes'] = donorTypes
     d['acceptorTypes'] = acceptorTypes
     d['distOnly'] = distOnly
     self.distSelector = DistanceSelector(return_dist=0)
Example #2
0
 def __init__(self, receptor_file, percentCutoff=1., detect_pi=False, dist_cutoff=6, verbose=False, 
                     distanceSelector=None, hydrogen_bond_builder=None, distanceSelectorWithCutoff=None,
                     aromatic_cycle_bond_selector=None): 
     self.receptor_file = receptor_file
     receptor = Read(receptor_file)
     assert(len(receptor)==1)
     assert isinstance(receptor, MoleculeSet)
     self.macro = receptor[0]
     self.macro_atoms = self.macro.allAtoms
     self.macro.buildBondsByDistance()
     self.verbose = verbose
     #??useful??
     self.percentCutoff = percentCutoff
     self.detect_pi = detect_pi
     self.distanceSelector = distanceSelector 
     if self.distanceSelector is None:
         self.distanceSelector = CloserThanVDWSelector(return_dist=0)
     self.hydrogen_bond_builder = hydrogen_bond_builder 
     if self.hydrogen_bond_builder is None:
         self.hydrogen_bond_builder = HydrogenBondBuilder()
     self.distanceSelectorWithCutoff = distanceSelectorWithCutoff 
     if self.distanceSelectorWithCutoff is None:
         self.distanceSelectorWithCutoff = DistanceSelector()
     self.dist_cutoff=float(dist_cutoff)
     self.results = d = {}
     self.report_list =['lig_hb_atoms','lig_close_atoms']
     self.aromatic_cycle_bond_selector = aromatic_cycle_bond_selector
     if self.aromatic_cycle_bond_selector is None:
         self.aromatic_cycle_bond_selector = AromaticCycleBondSelector()
     if detect_pi:
         self.report_list.extend(['pi_cation','pi_pi', 'cation_pi', 't_shaped'])
     if self.verbose: print("self.report_list=", self.report_list)
 def __init__(self, receptor_file, percentCutoff=1., detect_pi=False, dist_cutoff=6, verbose=False, 
                     distanceSelector=None, hydrogen_bond_builder=None, distanceSelectorWithCutoff=None,
                     aromatic_cycle_bond_selector=None): 
     self.receptor_file = receptor_file
     receptor = Read(receptor_file)
     assert(len(receptor)==1)
     assert isinstance(receptor, MoleculeSet)
     self.macro = receptor[0]
     self.macro_atoms = self.macro.allAtoms
     self.macro.buildBondsByDistance()
     self.verbose = verbose
     #??useful??
     self.percentCutoff = percentCutoff
     self.detect_pi = detect_pi
     self.distanceSelector = distanceSelector 
     if self.distanceSelector is None:
         self.distanceSelector = CloserThanVDWSelector(return_dist=0)
     self.hydrogen_bond_builder = hydrogen_bond_builder 
     if self.hydrogen_bond_builder is None:
         self.hydrogen_bond_builder = HydrogenBondBuilder()
     self.distanceSelectorWithCutoff = distanceSelectorWithCutoff 
     if self.distanceSelectorWithCutoff is None:
         self.distanceSelectorWithCutoff = DistanceSelector()
     self.dist_cutoff=float(dist_cutoff)
     self.results = d = {}
     self.report_list =['lig_hb_atoms','lig_close_atoms']
     self.aromatic_cycle_bond_selector = aromatic_cycle_bond_selector
     if self.aromatic_cycle_bond_selector is None:
         self.aromatic_cycle_bond_selector = AromaticCycleBondSelector()
     if detect_pi:
         self.report_list.extend(['pi_cation','pi_pi', 'cation_pi', 't_shaped'])
     if self.verbose: print "self.report_list=", self.report_list
 def __init__(self, distCutoff=distCutoff, distCutoff2=distCutoff2,
                     d2min=d2min, d2max=d2max, d3min=d3min, d3max=d3max,
                     a2min=a2min, a2max=a2max, a3min=a3min, a3max=a3max,
                     donorTypes=allDonors, acceptorTypes=allAcceptors,
                     distOnly=False):
     d = self.paramDict = {}
     d['distCutoff'] = distCutoff
     d['distCutoff2'] = distCutoff2
     d['d2min'] = d2min
     d['d2max'] = d2max
     d['d3min'] = d3min
     d['d3max'] = d3max
     d['a2min'] = a2min
     d['a2max'] = a2max
     d['a3min'] = a3min
     d['a3max'] = a3max
     d['donorTypes'] = donorTypes
     d['acceptorTypes'] = acceptorTypes
     d['distOnly'] = distOnly
     self.distSelector = DistanceSelector(return_dist=0)
Example #5
0
class HydrogenBondBuilder:
    """
    object which can build hydrogen bonds between atoms according
    to their coords and atom type
    """
    def __init__(self,
                 distCutoff=distCutoff,
                 distCutoff2=distCutoff2,
                 d2min=d2min,
                 d2max=d2max,
                 d3min=d3min,
                 d3max=d3max,
                 a2min=a2min,
                 a2max=a2max,
                 a3min=a3min,
                 a3max=a3max,
                 donorTypes=allDonors,
                 acceptorTypes=allAcceptors,
                 distOnly=False):
        d = self.paramDict = {}
        d['distCutoff'] = distCutoff
        d['distCutoff2'] = distCutoff2
        d['d2min'] = d2min
        d['d2max'] = d2max
        d['d3min'] = d3min
        d['d3max'] = d3max
        d['a2min'] = a2min
        d['a2max'] = a2max
        d['a3min'] = a3min
        d['a3max'] = a3max
        d['donorTypes'] = donorTypes
        d['acceptorTypes'] = acceptorTypes
        d['distOnly'] = distOnly
        self.distSelector = DistanceSelector(return_dist=0)

    def check_babel_types(self, ats):
        num_ats = len(ats)
        num_babel_type = len(ats.get(lambda x: hasattr(x, 'babel_type')))
        num_bnd_type = len(ats.get(lambda x: hasattr(x, 'bnd_type')))
        if (num_babel_type != num_ats) or (num_bnd_type != num_ats):
            babel = AtomHybridization()
            bond_orderer = BondOrder()
            tops = ats.top.uniq()
            for mol in tops:
                babel.assignHybridization(mol.allAtoms)
                bond_orderer.assignBondOrder(mol.allAtoms,
                                             mol.allAtoms.bonds[0])
                mol.allAtoms._bndtyped = 1

    def reset(self, ats):
        tops = ats.top.uniq()
        for mol in tops:
            for a in mol.allAtoms:
                if hasattr(a, 'hbonds'):
                    for item in a.hbonds:
                        del item
                    delattr(a, 'hbonds')

    def build(self, group1, group2=None, reset=True, paramDict=None):
        """atDict <- build(group1, group2, reset, paramDict=None, **kw):
            group1: atoms 
            group2: atoms 
            reset: remove all previous hbonds, default True!

            paramDict: a dictionary with these keys and default values
            distCutoff: 2.25  hydrogen--acceptor distance
            distCutoff2: 3.00 donor... acceptor distance
            d2min: 120 <min theta for sp2 hybridized donors>
            d2max: 180 <max theta for sp2 hybridized donors>
            d3min: 120 <min theta for sp3 hybridized donors>
            d3max: 170 <max theta for sp3 hybridized donors>

            a2min: 120 <min phi for sp2 hybridized donors>
            a2max: 150 <max phi for sp2 hybridized donors>
            a3min: 100 <min phi for sp3 hybridized donors>
            a3max: 150 <max phi for sp3 hybridized donors>
            @@FIX THIS: these do not seem to be input here
            donorTypes = allDonors
            acceptorTypes = allAcceptors
           """
        # setup parameter dictionary
        if paramDict is None:
            paramDict = self.paramDict

        # setup group2
        if group2 is None:
            group2 = group1

        # process each group
        # group1
        if group1.__class__ != Atom:
            group1 = group1.findType(Atom)
            # print "now group1=", group1.full_name()
            if not len(group1):
                return "ERROR"  # @@ OR WHAT?
        self.check_babel_types(group1)

        # group2
        if group2.__class__ != Atom:
            group2 = group2.findType(Atom)
            # print "now group2=", group2.full_name()
            if not len(group2):
                return "ERROR"  # @@ OR WHAT?
        self.check_babel_types(group2)

        if reset:
            # do optional reset: remove all prior hbonds
            self.reset(group1)
            self.reset(group2)

        # print "group1=", len(group1)
        # print "group2=", len(group2)

        # buildHbonds
        atDict = {}
        dict1 = self.buildD(group1, paramDict)

        # @@what is this doing here???
        # sp2 hybridized atoms
        # dAts2 = ats.get(lambda x, l=sp2: x.babel_type in l)
        if group1 == group2:
            # only one step:
            atD1 = self.process(dict1, dict1, paramDict)
            # print 'len(atD1).keys()=', len(atD1.keys())
            atD2 = {}
        else:
            # two steps:
            # 1: group1 donors v group2 acceptors
            # 2: group1 acceptors vs group2 donors
            dict2 = self.buildD(group2, paramDict)
            atD1 = self.process(dict1, dict2, paramDict)
            atD2 = self.process(dict2, dict1, paramDict)
            # print 'len(atD1).keys()=', len(atD1.keys())
            # print 'len(atD2).keys()=', len(atD2.keys())
        # if called with 1 atom could get tuple of two empty dictionaries
        if type(atD1) == type(atDict):
            if len(atD1):
                atDict.update(atD1)
        if type(atD2) == type(atDict):
            if len(atD2):
                atDict.update(atD2)
        # @@DESCRIBE atDict
        # this dict has atomSets as keys and as values(?check that)
        return atDict

    def checkForPossibleH(self, ats, blen):
        # @@FIX THIS: WHAT IS THE POINT OF THIS???
        # check that if at has all bonds, at least one is to a hydrogen
        # have to do this by element??
        probAts = AtomSet(ats.get(lambda x, blen=blen: len(x.bonds) == blen))
        # probOAts = ats.get(lambda x, blen=blen: len(x.bonds)==blen)
        # probSAts = ats.get(lambda x, blen=blen: len(x.bonds)==blen)
        if probAts:
            rAts = AtomSet([])
            for at in probAts:
                if not len(at.findHydrogens()):
                    rAts.append(at)
            if len(rAts):
                ats = ats.subtract(rAts)
        return ats

    def getHBDonors(self, ats, donorList):
        # getHBDonors
        sp2 = []
        sp3 = []
        for item in sp2Donors:
            if item in donorList:
                sp2.append(item)
        for item in sp3Donors:
            if item in donorList:
                sp3.append(item)

        dAts2 = ats.get(lambda x, l=sp2: x.babel_type in l)
        if not dAts2:
            dAts2 = AtomSet([])
        else:
            dAts2 = self.checkForPossibleH(dAts2, 3)

        dAts3 = ats.get(lambda x, l=sp3: x.babel_type in l)
        if not dAts3:
            dAts3 = AtomSet([])
        else:
            dAts3 = self.checkForPossibleH(dAts3, 4)
        return dAts2, dAts3

    def filterAcceptors(self, accAts):
        ntypes = ['Npl', 'Nam']
        npls = accAts.get(lambda x, ntypes=ntypes: x.babel_type == 'Npl')
        nams = accAts.get(lambda x, ntypes=ntypes: x.babel_type == 'Nam')
        # nAts = accAts.get(lambda x, ntypes=ntypes: x.babel_type in ntypes)
        restAts = accAts.get(
            lambda x, ntypes=ntypes: x.babel_type not in ntypes)
        if not restAts:
            restAts = AtomSet([])
        # if nAts:
        if npls:
            # for at in nAts:
            for at in npls:
                s = 0
                for b in at.bonds:
                    if b.bondOrder == 'aromatic':
                        s = s + 2
                    else:
                        s = s + b.bondOrder
                # if s<3:
                # apparently this is wrong
                if s < 4:
                    restAts.append(at)
        if nams:
            # for at in nAts:
            for at in nams:
                s = 0
                for b in at.bonds:
                    if b.bondOrder == 'aromatic':
                        s = s + 2
                    else:
                        s = s + b.bondOrder
                    # s = s + b.bondOrder
                if s < 3:
                    restAts.append(at)
        return restAts

    def getHBAcceptors(self, ats, acceptorList):
        # print "getHBAcceptors: acceptorList=", acceptorList
        # getHBAcceptors
        sp2 = []
        sp3 = []
        for item in sp2Acceptors:
            if item in acceptorList:
                sp2.append(item)
        for item in sp3Acceptors:
            if item in acceptorList:
                sp3.append(item)

        dAts2 = AtomSet(ats.get(lambda x, l=sp2: x.babel_type in l))
        if dAts2:
            dAts2 = self.filterAcceptors(dAts2)
        dAts3 = AtomSet(ats.get(lambda x, l=sp3: x.babel_type in l))
        return dAts2, dAts3

    def buildD(self, ats, paramDict=None):
        if paramDict is None:
            paramDict = self.paramDict
        # these are from the __call__ method of vf.buildHBonds
        if 'distCutoff' not in paramDict:
            paramDict['distCutoff'] = 2.25
        if 'distCutoff2' not in paramDict:
            paramDict['distCutoff2'] = 3.00
        if 'd2min' not in paramDict:
            paramDict['d2min'] = 120.
        if 'd2max' not in paramDict:
            paramDict['d2max'] = 180.
        if 'd3min' not in paramDict:
            paramDict['d3min'] = 120.
        if 'd3max' not in paramDict:
            paramDict['d3max'] = 170.
        if 'a2min' not in paramDict:
            paramDict['a2min'] = 130.
        if 'a2max' not in paramDict:
            paramDict['a2max'] = 170.
        if 'a3min' not in paramDict:
            paramDict['a3min'] = 120.
        if 'a3max' not in paramDict:
            paramDict['a3max'] = 170.
        if 'distOnly' not in paramDict:
            paramDict['distOnly'] = 0
        if 'donorTypes' not in paramDict:
            paramDict['donorTypes'] = allDonors
        if 'acceptorTypes' not in paramDict:
            paramDict['acceptorTypes'] = allAcceptors

        d = {}
        donorTypes = paramDict['donorTypes']
        donor2Ats, donor3Ats = self.getHBDonors(ats, donorTypes)
        d23 = donor2Ats + donor3Ats
        # hAts = ats.get(lambda x, d23=d23: x.element=='H' \
        # and x.bonds[0].neighborAtom(x) in d23)
        hydrogen_atoms = ats.get(lambda x: x.element == 'H' and len(x.bonds))
        # hAts = AtomSet(ats.get(lambda x, donorTypes=donorTypes: x.element=='H' \
        hAts = AtomSet(
            hydrogen_atoms.get(
                lambda x, donorTypes=donorTypes: x.bonds[0].atom1.babel_type in
                donorTypes or x.bonds[0].atom2.babel_type in donorTypes))
        d['hAts'] = hAts
        d['donor2Ats'] = donor2Ats
        d['donor3Ats'] = donor3Ats

        acceptorTypes = paramDict['acceptorTypes']
        # print "about to call getHBAcceptors with acceptorTypes=", acceptorTypes
        acceptor2Ats, acceptor3Ats = self.getHBAcceptors(ats, acceptorTypes)
        d['acceptor2Ats'] = acceptor2Ats
        d['acceptor3Ats'] = acceptor3Ats
        if acceptor2Ats:
            acceptorAts = acceptor2Ats
            if acceptor3Ats:
                acceptorAts = acceptorAts + acceptor3Ats
        elif acceptor3Ats:
            acceptorAts = acceptor3Ats
        else:
            # CHECK THIS: should it be None or AtomSet([])
            acceptorAts = None
        d['acceptorAts'] = acceptorAts
        return d

    def getMat(self, ats):
        pass
        # tops = ats.top.uniq()
        # if len(tops)>1:
        #    self.warningMsg('transformation mat=None:>1 mol in atomset!')
        #    return None
        # g = tops[0].geomContainer.geoms['master']
        # return g.GetMatrix(g)

    def process(self, dict1, dict2, paramDict):
        # hAts are keys, aceptorAts are checks
        hAts = dict1['hAts']
        tAts = hAts
        dist = paramDict['distCutoff']
        distOnly = paramDict['distOnly']

        if not hAts:
            # then use donors and a different distance
            tAts = dict1['donor2Ats'] + dict1['donor3Ats']
            dist = paramDict['distCutoff2']

        acceptorAts = dict2['acceptorAts']
        # print "acceptorAts=", acceptorAts
        if not acceptorAts or not tAts:  # 6/14/2004
            return {}, {}

        # call distanceSelector on two groups of atoms with dist
        # keyMat = self.getMat(tAts)
        # checkMat = self.getMat(acceptorAts)
        atDict = self.distSelector.select(tAts, acceptorAts, dist)
        #    keyMat=keyMat, checkMat=checkMat)
        # atDict = self.distSelector.select(tAts, acceptorAts, dist)
        # first remove bonded angles
        atDict = self.removeNeighbors(atDict)

        donor2Ats = dict1['donor2Ats']
        donor3Ats = dict1['donor3Ats']
        acceptor2Ats = dict2['acceptor2Ats']
        acceptor3Ats = dict2['acceptor3Ats']

        if distOnly:
            # need to build hbonds and return dictionary
            self.makeBonds(atDict, donor2Ats, donor3Ats, acceptor2Ats,
                           acceptor3Ats, paramDict)
            return atDict

        badAtDict = self.filterBasedOnAngs(atDict, donor2Ats, donor3Ats,
                                           acceptor2Ats, acceptor3Ats,
                                           paramDict)
        atDict = self.removeBadAts(atDict, badAtDict)
        if atDict is None:
            atDict = {}
        return atDict

    def makeBonds(self, pD, d2Ats, d3Ats, a2Ats, a3ats, paramDict):
        for k in list(pD.keys()):
            if k.element == 'H':
                if hasattr(k, 'hbonds') and len(k.hbonds):
                    continue
                d = k.bonds[0].atom1
                if id(d) == id(k):
                    d = k.bonds[0].atom2
                # d = k.bonds[0].neighborAtom(k)
                h = k
            else:
                d = k
                h = None
            # pD[k] is a list of close-enough ats
            for ac in pD[k]:
                if ac == d:
                    continue
                dSp2 = d in d2Ats
                aSp2 = ac in a2Ats
                if dSp2:
                    if aSp2:
                        typ = 22
                    else:
                        typ = 23
                elif aSp2:
                    typ = 32
                else:
                    typ = 33
                # THEY could be already bonded
                alreadyBonded = 0
                if hasattr(d, 'hbonds') and hasattr(ac, 'hbonds'):
                    for hb in d.hbonds:
                        if hb.donAt == ac or hb.accAt == ac:
                            alreadyBonded = 1

                if not alreadyBonded:
                    newHB = HydrogenBond(d, ac, h, typ=typ)
                    if not hasattr(ac, 'hbonds'):
                        ac.hbonds = []
                    if not hasattr(d, 'hbonds'):
                        d.hbonds = []
                    ac.hbonds.append(newHB)
                    d.hbonds.append(newHB)
                    if h is not None:
                        # hydrogens can have only 1 hbond
                        h.hbonds = [newHB]

    def filterBasedOnAngs(self, pD, d2Ats, d3Ats, a2Ats, a3ats, paramDict):
        badAtDict = {}
        d2max = paramDict['d2max']
        d2min = paramDict['d2min']
        d3max = paramDict['d3max']
        d3min = paramDict['d3min']
        # NEED these parameters
        a2max = paramDict['a2max']
        a2min = paramDict['a2min']
        a3max = paramDict['a3max']
        a3min = paramDict['a3min']
        # NB now pD keys could be hydrogens OR donors
        for k in list(pD.keys()):
            if k.element == 'H':
                d = k.bonds[0].atom1
                if id(d) == id(k):
                    d = k.bonds[0].atom2
                # d = k.bonds[0].neighborAtom(k)
                h = k
            else:
                d = k
                h = None
            badAts = AtomSet([])
            ct = 0
            for ac in pD[k]:
                if h is not None:
                    ang = getAngle(ac, h, d)
                else:
                    acN = ac.bonds[0].atom1
                    if id(acN) == id(ac):
                        acN = ac.bonds[0].atom2
                    # acN = ac.bonds[0].neighborAtom(ac)
                    ang = getAngle(d, ac, acN)
                # print 'ang=', ang
                dSp2 = d in d2Ats
                aSp2 = ac in a2Ats
                # these limits could be adjustable
                if h is not None:
                    if dSp2:
                        upperLim = d2max
                        lowerLim = d2min
                        # upperLim = 170
                        # lowerLim = 130
                    else:
                        upperLim = d3max
                        lowerLim = d3min
                        # upperLim = 180
                        # lowerLim = 120
                else:
                    # if there is no hydrogen use d-ac-acN angles
                    if dSp2:
                        upperLim = a2max
                        lowerLim = a2min
                        # upperLim = 150
                        # lowerLim = 110
                    else:
                        upperLim = a3max
                        lowerLim = a3min
                        # upperLim = 150
                        # lowerLim = 100
                if ang > lowerLim and ang < upperLim:
                    # AT THIS MOMENT BUILD HYDROGEN BOND:
                    if dSp2:
                        if aSp2:
                            typ = 22
                        else:
                            typ = 23
                    elif aSp2:
                        typ = 32
                    else:
                        typ = 33
                    # THEY could be already bonded
                    alreadyBonded = 0
                    if hasattr(d, 'hbonds') and hasattr(ac, 'hbonds'):
                        for hb in d.hbonds:
                            if hb.donAt == ac or hb.accAt == ac:
                                alreadyBonded = 1
                    if not alreadyBonded:
                        newHB = HydrogenBond(d, ac, h, theta=ang, typ=typ)
                        if not hasattr(ac, 'hbonds'):
                            ac.hbonds = []
                        if not hasattr(d, 'hbonds'):
                            d.hbonds = []
                        ac.hbonds.append(newHB)
                        d.hbonds.append(newHB)
                        if h is not None:
                            # hydrogens can have only 1 hbond
                            h.hbonds = [newHB]
                        #    newHB.hlen = dist
                        # else:
                        #    newHB.dlen = dist
                else:
                    badAts.append(ac)
                ct = ct + 1
            badAtDict[k] = badAts
        return badAtDict

    def removeBadAts(self, atDict, badAtDict):
        # clean-up function called after filtering on angles
        badKeys = list(badAtDict.keys())
        for at in list(atDict.keys()):
            if at not in badKeys:
                continue
            if not len(badAtDict[at]):
                continue
            closeAts = atDict[at]
            badAts = badAtDict[at]
            goodAts = []
            for i in range(len(closeAts)):
                cAt = closeAts[i]
                if cAt not in badAts:
                    goodAts.append(cAt)
            if len(goodAts):
                atDict[at] = goodAts
            else:
                del atDict[at]
        return atDict

    def removeNeighbors(self, atDict):
        # filter out at-itself and at-bondedat up to 1:4
        # NB keys could be hydrogens OR donors
        for at in list(atDict.keys()):
            closeAts = atDict[at]
            bondedAts = AtomSet([])
            for b in at.bonds:
                ###at2 = b.neighborAtom(at)
                at2 = b.atom1
                if id(at2) == id(at):
                    at2 = b.atom2
                bondedAts.append(at2)
                # 9/13 remove this:
                ##also remove 1-3
                for b2 in at2.bonds:
                    at3 = b2.atom1
                    if id(at3) == id(at2):
                        at3 = b.atom2
                    # at3 = b2.neighborAtom(at2)
                    if id(at3) != id(at):
                        bondedAts.append(at3)
                    # for b3 in at3.bonds:
                    # at4 = b2.neighborAtom(at3)
                    # if at4!=at and at4!=at2:
                    # bondedAts.append(at4)
            bondedAts = bondedAts.uniq()
            goodAts = []
            for i in range(len(closeAts)):
                cAt = closeAts[i]
                if cAt not in bondedAts:
                    goodAts.append(cAt)
            if len(goodAts):
                atDict[at] = goodAts
            else:
                del atDict[at]
        return atDict

    def getDonors(self, nodes, paramDict):
        donorList = paramDict['donorTypes']
        # print 'donorList=', donorList
        # currently this is a set of hydrogens
        hats = AtomSet(nodes.get(lambda x: x.element == 'H'))
        # hats are optional: if none, process donors
        # if there are hats: dAts are all atoms bonded to all hydrogens
        if hats:
            dAts = AtomSet([])
            for at in hats:
                for b in at.bonds:
                    at2 = b.atom1
                    if id(at2) == id(at):
                        at2 = b.atom2
                    dAts.append(at2)
                    # dAts.append(b.neighborAtom(at))
        else:
            dAts = nodes
        # get the sp2 hybridized possible donors which are all ns
        sp2 = []
        for t in ['Nam', 'Ng+', 'Npl']:
            if t in donorList:
                sp2.append(t)
        # ntypes = ['Nam', 'Ng+', 'Npl']

        sp2DAts = None
        if len(sp2):
            sp2DAts = AtomSet(dAts.get(lambda x, sp2=sp2: x.babel_type in sp2))

        hsp2 = AtomSet([])
        if sp2DAts:
            if hats:
                hsp2 = AtomSet(
                    hats.get(lambda x, sp2DAts=sp2DAts: x.bonds[0].atom1 in
                             sp2DAts or x.bonds[0].atom2 in sp2DAts))
        if sp2DAts:
            # remove any sp2 N atoms which already have 3 bonds not to hydrogens
            n2Dons = AtomSet(sp2DAts.get(lambda x: x.element == 'N'))
            if n2Dons:
                n2Dons.bl = 0
                for at in n2Dons:
                    for b in at.bonds:
                        if type(b.bondOrder) == type(2):
                            at.bl = at.bl + b.bondOrder
                        else:
                            at.bl = at.bl + 2
                        # allow that there might already be a hydrogen
                    nH = at.findHydrogens()
                    at.bl = at.bl - len(nH)
                badAts = AtomSet(n2Dons.get(lambda x: x.bl > 2))
                if badAts:
                    sp2DAts = sp2DAts - badAts
                delattr(n2Dons, 'bl')
        # get the sp3 hybridized possible donors
        sp3 = []
        for t in ['N3+', 'S3', 'O3']:
            if t in donorList:
                sp3.append(t)
        n3DAts = None
        if 'N3+' in sp3:
            n3DAts = AtomSet(dAts.get(lambda x: x.babel_type == 'N3+'))
        o3DAts = None
        if 'O3' in sp3:
            o3DAts = AtomSet(dAts.get(lambda x: x.babel_type == 'O3'))
        if o3DAts:
            # remove any O3 atoms which already have 2 bonds not to hydrogens
            badO3s = AtomSet([])
            for at in o3DAts:
                if len(at.bonds) < 2:
                    continue
                if len(at.findHydrogens()):
                    continue
                else:
                    badO3s.append(at)
            if len(badO3s):
                o3DAts = o3DAts - badO3s
        s3DAts = None
        if 'S3' in sp3:
            s3DAts = AtomSet(dAts.get(lambda x: x.babel_type == 'S3'))
        sp3DAts = AtomSet([])
        for item in [n3DAts, o3DAts, s3DAts]:
            if item:
                sp3DAts = sp3DAts + item
        hsp3 = AtomSet([])
        if sp3DAts:
            if hats:
                hsp3 = AtomSet(
                    hats.get(lambda x, sp3DAts=sp3DAts: x.bonds[0].atom1 in
                             sp3DAts or x.bonds[0].atom2 in sp3DAts))
        hsp = hsp2 + hsp3
        # print 'hsp=', hsp.name
        # print 'sp2DAts=', sp2DAts.name
        # print 'sp3DAts=', sp3DAts.name
        return hsp, sp2DAts, sp3DAts

    def getAcceptors(self, nodes, paramDict):
        acceptorList = paramDict['acceptorTypes']
        # print 'acceptorList=', acceptorList

        sp2 = []
        for t in ['Npl', 'Nam']:
            if t in acceptorList:
                sp2.append(t)
        n2Accs = None
        if 'Npl' in sp2:
            n2Accs = AtomSet(nodes.get(lambda x: x.babel_type == 'Npl'))
        if 'Nam' in sp2:
            n2Accs2 = AtomSet(nodes.get(lambda x: x.babel_type == 'Nam'))
            if n2Accs2:
                if n2Accs:
                    n2Accs = n2Accs + n2Accs2
                else:
                    n2Accs = n2Accs2
        if n2Accs is None:
            n2Accs = AtomSet([])

        o_sp2 = []
        for t in ['O2', 'O-']:
            if t in acceptorList:
                sp2.append(t)

        o2Accs = None
        if 'O2' in o_sp2:
            o2Accs = AtomSet(nodes.get(lambda x: x.babel_type == 'O2'))
        if 'O-' in sp2:
            o2Accs2 = AtomSet(nodes.get(lambda x: x.babel_type == 'O-'))
            if o2Accs2:
                if o2Accs:
                    o2Accs = o2Accs + o2Accs2
                else:
                    o2Accs = o2Accs2
        if o2Accs is None:
            o2Accs = AtomSet([])

        o3Accs = None
        if 'O3' in acceptorList:
            o3Accs = AtomSet(nodes.get(lambda x: x.babel_type == 'O3'))
        if o3Accs is None:
            o3Accs = AtomSet([])

        s3Accs = None
        if 'S3' in acceptorList:
            s3Accs = AtomSet(nodes.get(lambda x: x.babel_type == 'S3'))
        if s3Accs is None:
            s3Accs = AtomSet([])

        ret2Ats = AtomSet([])
        for item in [n2Accs, o2Accs]:
            ret2Ats = ret2Ats + item

        ret3Ats = AtomSet([])
        for item in [s3Accs, o3Accs]:
            ret3Ats = ret3Ats + item
        if ret2Ats:
            print('ret2Ats=', ret2Ats.name)
        else:
            print('no ret2Ats')
        if ret3Ats:
            print('ret3Ats=', ret3Ats.name)
        else:
            print('no ret3Ats')
        return ret2Ats, ret3Ats
Example #6
0
class InteractionDetector:
    """
        Base class for object to detect interactions between a receptor and potential ligands... such as virtual screen results
        
        initialized with the receptor file containing entire molecule or residues of interest.
        processLigand method takes as input a pdbqt file containing docked coordinates and returns a string 
                        composed of  hbondStr + macrocloseContactStr + ligcloseContactStr
        If interections are found, a new pdbqt containing interaction description is also output: 
    """
    def __init__(self, receptor_file, percentCutoff=1., detect_pi=False, dist_cutoff=6, verbose=False, 
                        distanceSelector=None, hydrogen_bond_builder=None, distanceSelectorWithCutoff=None,
                        aromatic_cycle_bond_selector=None): 
        self.receptor_file = receptor_file
        receptor = Read(receptor_file)
        assert(len(receptor)==1)
        assert isinstance(receptor, MoleculeSet)
        self.macro = receptor[0]
        self.macro_atoms = self.macro.allAtoms
        self.macro.buildBondsByDistance()
        self.verbose = verbose
        #??useful??
        self.percentCutoff = percentCutoff
        self.detect_pi = detect_pi
        self.distanceSelector = distanceSelector 
        if self.distanceSelector is None:
            self.distanceSelector = CloserThanVDWSelector(return_dist=0)
        self.hydrogen_bond_builder = hydrogen_bond_builder 
        if self.hydrogen_bond_builder is None:
            self.hydrogen_bond_builder = HydrogenBondBuilder()
        self.distanceSelectorWithCutoff = distanceSelectorWithCutoff 
        if self.distanceSelectorWithCutoff is None:
            self.distanceSelectorWithCutoff = DistanceSelector()
        self.dist_cutoff=float(dist_cutoff)
        self.results = d = {}
        self.report_list =['lig_hb_atoms','lig_close_atoms']
        self.aromatic_cycle_bond_selector = aromatic_cycle_bond_selector
        if self.aromatic_cycle_bond_selector is None:
            self.aromatic_cycle_bond_selector = AromaticCycleBondSelector()
        if detect_pi:
            self.report_list.extend(['pi_cation','pi_pi', 'cation_pi', 't_shaped'])
        if self.verbose: print("self.report_list=", self.report_list)


    def processLigand(self, ligand_file, percentCutoff=None, output=1, outputfilename=None, comment="USER  AD> ", buildHB=1, remove_modelStr=False):
        #if outputfilename is not None, have to (1) update all the strings or (2) rename ligand
        self.results = d = {}
        ligand = Read(ligand_file)
        assert isinstance(ligand, MoleculeSet)
        assert(len(ligand)==1)
        ligand = ligand[0]
        first = ligand.name.find('_model')
        last = ligand.name.rfind('_model')
        if first!=last:
            ligand.name = ligand.name[:last]
        if remove_modelStr:
            curName = ligand.name
            modelIndex=curName.find("_model")
            if modelIndex>-1:
                curName = curName[:modelIndex]
            #setup outputfilestem
            ligand.name = curName
        ligand.buildBondsByDistance()
        if not percentCutoff:
            percentCutoff = self.percentCutoff
        # first detect sets of atoms forming hydrogen bonds
        # hbondStr,hblist and has_hbonds added to process vina results which do not include valid hydrogen bonds
        hbondStr = ""
        hblist = [""]
        has_hbonds = False
        if buildHB:
            has_hbonds = True
            hbondStr = self.buildHydrogenBonds(ligand, comment=comment)           #
            if self.verbose: print("hbondStr=", hbondStr)
            hblist = hbondStr.split('\n') # hbond info
            if hblist[0]=='lig_hb_atoms : 0':
                has_hbonds = False
        # next detect sets of atoms in close contact not forming hydrogen bonds
        macrocloseContactStr, ligcloseContactStr = self.buildCloseContactAtoms(percentCutoff, ligand, comment=comment)              #
        self.piResults = ""
        if self.detect_pi: 
            self.detectPiInteractions()
            if self.results['pi_cation']:
                self.piResults = self.get_pi_cation_result(print_ctr=1, comment=comment)
                if self.verbose: 
                    print("found pi_cation in ", ligand_file)
                    print("set self.piResults to ", self.piResults)
            if self.results['pi_pi']:
                self.piResults += self.get_pi_pi(print_ctr=1, comment=comment)
                if self.verbose: 
                    print("set self.piResults to ", self.piResults)
        macro_cclist = macrocloseContactStr.split(';')
        lig_cclist = ligcloseContactStr.split(';')
        if has_hbonds or len(macro_cclist):
            fptr = open(ligand_file)
            lines = fptr.readlines()
            fptr.close()
            if outputfilename is not None:
                optr = open(outputfilename, 'w')
            else:
                optr = open(ligand_file, 'w')
            for new_l in hblist:
                if not(len(new_l)): #don't output empty lines
                    continue
                if new_l.find(comment)!=0:
                    new_l = comment + new_l
                if new_l != hblist[-1]:
                    optr.write(new_l+"\n")
                else:
                    optr.write(new_l) # no newline after the last one
            for new_l in macro_cclist:
                if not(len(new_l)): #don't output empty lines
                    continue
                if new_l.find(comment)!=0:
                    new_l = comment + new_l
                if new_l != macro_cclist[-1]:
                    optr.write(new_l + "\n")
                else:
                    optr.write(new_l)
            for new_l in lig_cclist:
                if not(len(new_l)): #don't output empty lines
                    continue
                if new_l.find(comment)!=0:
                    new_l = comment + new_l
                if new_l != lig_cclist[-1]:
                    optr.write(new_l + "\n")
                else:
                    optr.write(new_l)
            if len(self.piResults): ##???
                for s in self.piResults.split("\n"):
                    optr.write(s+"\n")
            for l in lines:
                optr.write(l)
            optr.close()
        return hbondStr + macrocloseContactStr + ligcloseContactStr
            

    def getResultStr(self, verbose=False):
        #only write important ones: hbonds, vdw_contacts, ?pi_pi,pi_cation etc?
        ss = ""
        for k in self.report_list:
            v = self.results[k]
            #key:atom1.full_name();
            if len(v):
                if verbose: print("gRS: report for ", k)
                if verbose: print("USER:  "******":" + str(len(v))) 
                if verbose: print("     start ss= ", ss)
                ss +=  k + ":" + str(len(v))  + "\n"
                if k=='lig_hb_atoms':
                    if verbose: print("@@ getResultStr lig_hb_atoms")
                    hbstr = ""
                    for lig_at in v:
                        for hb in lig_at.hbonds:
                            if hasattr(hb, 'hAt'):
                                hbstr += 'USER %s-%s~%s\n'%(hb.donAt.full_name(), hb.hAt.full_name(),hb.accAt.full_name())
                            else:
                                hbstr += 'USER %s~%s\n'%(hb.donAt.full_name(), hb.accAt.full_name())
                    if verbose: print("hbstr=")
                    #add it to ss here
                    ss += hbstr
                    if verbose: print("with hbstr: ss=", ss)
                #if self.verbose:
                elif k == 'pi_cation':
                    for res in v:
                        #v=[(<Atom instance> HSG1:A:ARG8:CZ, [<Atom instance> IND: : INI 20:C1])]
                        #res=(<Atom instance> HSG1:A:ARG8:CZ, [<Atom instance> IND: : INI 20:C1])
                        #res[0]=<Atom instance> HSG1:A:ARG8:CZ
                        #res[1]=  [<Atom instance> IND: : INI 20:C1]
                        #res[1][0]=  <Atom instance> IND: : INI 20:C1
                        #
                        ss += "USER %s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
                else:
                    for w in v:
                        if verbose: print("@@ getResultStr w=", w)
                        try:
                            ss += 'USER ' + w.full_name()+'\n;'
                        except:
                            if verbose: print("except on ", w)
                            ss += "USER " + str(w)
                ss += "\n" 
                if verbose: print("     end ss= ", ss)
        return ss


    def buildHydrogenBonds(self, ligand, comment="USER AD> "):
        h_pairDict = self.hydrogen_bond_builder.build(ligand.allAtoms, self.macro_atoms)
        self.h_pairDict = h_pairDict
        #keys should be from lig, values from macro 
        #sometimes are not...@@check this@@
        h_results = {}
        for k, v in list(h_pairDict.items()):
            h_results[k] = 1
            for at in v:
                h_results[at] = 1
        all_hb_ats = AtomSet(list(h_results.keys()))  #all
        d = self.results
        macro_hb_ats = d['macro_hb_atoms'] = all_hb_ats.get(lambda x: x.top==self.macro)
        self.macro_hb_ats = macro_hb_ats
        # process lig
        lig_hb_ats = d['lig_hb_atoms'] = all_hb_ats.get(lambda x: x in ligand.allAtoms)
        self.lig_hb_ats = lig_hb_ats
        outS = comment + "lig_hb_atoms : %d\n"%(len(lig_hb_ats))
        for p in self.lig_hb_ats:  #intD.results['lig_hb_atoms']:
            for hb in p.hbonds:
                if hasattr(hb, 'used'): continue
                if hb.hAt is not None:
                    outS += comment + "%s,%s~%s\n"%(hb.donAt.full_name(), hb.hAt.name, hb.accAt.full_name())
                else:
                    outS += comment + "%s~%s\n"%(hb.donAt.full_name(), hb.accAt.full_name())
                hb.used = 1
                #hsg1V:B:ARG8:NH2,HH22~clean: : INI 20:N5
                #clean: : INI 20:O4,H3~hsg1V:B:ASP29:OD2
                #clean: : INI 20:O4,H3~hsg1V:B:ASP29:OD2
                #clean: : INI 20:N4,H3~hsg1V:B:GLY27:O
                #clean: : INI 20:O2,H2~hsg1V:B:ASP25:OD1
        #macroHStr = self.macro.allAtoms.get(lambda x: hasattr(x, 'hbonds') and len(x.hbonds)).full_name()
        #ligHStr = ligand.allAtoms.get(lambda x: hasattr(x, 'hbonds') and len(x.hbonds)).full_name()
        #return  macroHStr + '==' + ligHStr
        if self.verbose: 
            print("buildHB returning:")
            print(outS)
        return outS



    def buildCloseContactAtoms(self, percentCutoff, ligand, comment="USER AD> "):
        pairDict = self.distanceSelector.select(ligand.allAtoms,
                        self.macro_atoms, percentCutoff=percentCutoff)
        self.pairDict = pairDict
        #reset here
        lig_close_ats = AtomSet()
        macro_close_ats = AtomSet()
        cdict = {}
        for k,v in list(pairDict.items()):
            if len(v):
                cdict[k] = 1
            for at in v:
                if at not in macro_close_ats:
                    cdict[at] = 1
        closeAtoms = AtomSet(list(cdict.keys()))
        lig_close_ats = closeAtoms.get(lambda x: x.top==ligand).uniq()
        #ligClAtStr = lig_close_ats.full_name()
        ligClAtStr = comment + "lig_close_ats: %d\n" %( len(lig_close_ats))
        if len(lig_close_ats):
            ligClAtStr += comment + "%s\n" %( lig_close_ats.full_name())
        macro_close_ats = closeAtoms.get(lambda x: x in self.macro_atoms).uniq()
        macroClAtStr = comment + "macro_close_ats: %d\n" %( len(macro_close_ats))
        if len(macro_close_ats):
            macroClAtStr += comment + "%s\n" %( macro_close_ats.full_name())
        #macroClAtStr = "macro_close_ats: " + len(macro_close_ats)+"\n" +macro_close_ats.full_name()
        rdict = self.results
        rdict['lig_close_atoms'] = lig_close_ats
        rdict['macro_close_atoms'] = macro_close_ats
        if self.verbose: print("macroClAtStr=", macroClAtStr)
        if self.verbose: print("ligClAtStr=", ligClAtStr)
        if self.verbose: print("returning "+ macroClAtStr + '==' + ligClAtStr)
        return  macroClAtStr , ligClAtStr


    def getCations(self, atoms):
        #select atoms in ARG and LYS residues
        arg_cations = atoms.get(lambda x: (x.parent.type=='ARG' and \
                                x.name in ['CZ']))
        lys_cations = atoms.get(lambda x: (x.parent.type=='LYS' and \
                                x.name in ['NZ', 'HZ1', 'HZ2', 'HZ3']))
        #select any positively-charged metal ions... cannot include CA here
        metal_cations = atoms.get(lambda x: x.name in ['Mn','MN', 'Mg',\
                                'MG', 'FE', 'Fe', 'Zn', 'ZN'])
        ca_cations = atoms.get(lambda x: x.name in ['CA', 'Ca'] and x.parent.type=='CA')
        cations = AtomSet() 
        #cations.extend(arg_cations)
        for a in arg_cations:
            cations.append(a)
        #cations.extend(lys_cations)
        for a in lys_cations:
            cations.append(a)
        #cations.extend(metal_cations)
        for a in metal_cations:
            cations.append(a)
        #cations.extend(ca_cations)
        for a in ca_cations:
            cations.append(a)
        return cations


    def detectPiInteractions(self, tolerance=0.95, debug=False, use_all_cycles=False):
        if debug: print("in detectPiInteractions")
        self.results['pi_pi'] = []        #stacked rings...?
        self.results['t_shaped'] = []     #one ring perpendicular to the other
        self.results['cation_pi'] = []    #
        self.results['pi_cation'] = []    #
        self.results['macro_cations'] = []#
        self.results['lig_cations'] = []  #
        #at this point have self.results
        if not len(self.results['lig_close_atoms']):
            return
        lig_atoms = self.results['lig_close_atoms'].parent.uniq().atoms
        macro_res = self.results['macro_close_atoms'].parent.uniq()
        if not len(macro_res):
            return
        macro_atoms = macro_res.atoms
        l_rf = RingFinder()
        #Ligand
        l_rf.findRings2(lig_atoms, lig_atoms.bonds[0])
        #rf.rings is list of dictionaries, one per ring, with keys 'bonds' and 'atoms'
        if debug: print("LIG: len(l_rf.rings)=", len(l_rf.rings))
        if not len(l_rf.rings):
            if debug: print("no lig rings found by l_rf!")
            return
        acbs = self.aromatic_cycle_bond_selector
        #acbs = AromaticCycleBondSelector()
        lig_rings = []
        for r in l_rf.rings:
            ring_bnds = r['bonds']
            if use_all_cycles: 
                lig_rings.append(ring_bnds)
            else:
                arom_bnds = acbs.select(ring_bnds)
                if len(arom_bnds)>4:
                    lig_rings.append(arom_bnds)
        if debug: print("LIG: len(lig_arom_rings)=", len(lig_rings))
        self.results['lig_rings'] = lig_rings
        self.results['lig_ring_atoms'] = AtomSet()
        #only check for pi-cation if lig_rings exist
        if len(lig_rings):
            macro_cations = self.results['macro_cations'] = self.getCations(macro_atoms)
            macro_cations = macro_cations.get(lambda x: x.element!='H')
            lig_ring_atoms = AtomSet()
            u = {}
            for r in lig_rings:
                for a in BondSet(r).getAtoms():
                    u[a] = 1
            if len(u): 
                lig_ring_atoms = AtomSet(list(u.keys()))
                lig_ring_atoms.sort()
                self.results['lig_ring_atoms'] = lig_ring_atoms
            if len(macro_cations):
                if debug: print("check distances from lig_rings to macro_cations here")
                #macro cations->lig rings
                pairDict2 = self.distanceSelector.select(lig_ring_atoms,macro_cations)
                z = {}
                for key,v in list(pairDict2.items()):
                    val = v.tolist()[0]
                    if val in macro_cations:
                        z[val] = [key]
                if len(z):
                    self.results['pi_cation'] = (list(z.items()))
                else:
                    self.results['pi_cation'] = []
        #check the distance between the rings and the macro_cations
        self.results['lig_cations'] = self.getCations(lig_atoms)
        lig_cations = self.results['lig_cations'] 
        #remove hydrogens
        lig_cations = lig_cations.get(lambda x: x.element!='H')
        #Macromolecule
        m_rf = RingFinder()
        m_rf.findRings2(macro_res.atoms, macro_res.atoms.bonds[0])
        #rf.rings is list of dictionaries, one per ring, with keys 'bonds' and 'atoms'
        if debug: print("MACRO: len(m_rf.rings)=", len(m_rf.rings))
        if not len(m_rf.rings):
            if debug: print("no macro rings found by m_rf!")
            return
        macro_rings = []
        for r in m_rf.rings:
            ring_bnds = r['bonds']
            if use_all_cycles: 
                macro_rings.append(ring_bnds)
            else:
                arom_bnds = acbs.select(ring_bnds)
                if len(arom_bnds)>4:
                    macro_rings.append(arom_bnds)
        if debug: print("len(macro_arom_rings)=", len(macro_rings))
        self.results['macro_rings'] = macro_rings
        self.results['macro_ring_atoms'] = AtomSet()
        #only check for pi-cation if macro_rings exist
        if len(macro_rings):
            macro_ring_atoms = AtomSet()
            u = {}
            for r in macro_rings:
                for a in BondSet(r).getAtoms(): #new method of bondSets
                    u[a] = 1
            if len(u):
                macro_ring_atoms = AtomSet(list(u.keys()))
                macro_ring_atoms.sort()
                self.results['macro_ring_atoms'] = macro_ring_atoms
            if len(lig_cations):
                if debug: print("check distances from macro_rings to lig_cations here")
                pairDict3 = self.distanceSelector.select(macro_ring_atoms,lig_cations)
                z = {}
                for x in list(pairDict3.items()):
                    #lig cations->macro rings
                    z.setdefault(x[1].tolist()[0], []).append(x[0])
                if len(z):
                    self.results['cation_pi'] = (list(z.items()))
                else:
                    self.results['cation_pi'] = []
                #macro_pi_atoms = AtomSet(pairDict3.keys())
                #l_cations = AtomSet()
                #for v in pairDict3.values():
                #    for x in v:
                #        l_cations.append(x)
                #self.results['cation_pi'] = pairDict3.items()
                #self.results['cation_pi'] = (l_cations, macro_pi_atoms)
        #check for intermol distance <6 Angstrom (J.ComputChem 29:275-279, 2009)
        #compare each lig_ring vs each macro_ring
        for lig_ring_bnds in lig_rings:
            lig_atoms = acbs.getAtoms(lig_ring_bnds)
            lig_atoms.sort()
            if debug: print("len(lig_atoms)=", len(lig_atoms))
            #---------------------------------
            # compute the normal to lig ring
            #---------------------------------
            a1 = numpy.array(lig_atoms[0].coords)
            a2 = numpy.array(lig_atoms[2].coords)
            a3 = numpy.array(lig_atoms[4].coords)
            if debug: print("a1,a2, a3=", a1.tolist(), a2.tolist(), a3.tolist())
            for macro_ring_bnds in macro_rings:
                macro_atoms = acbs.getAtoms(macro_ring_bnds)
                macro_atoms.sort()
                if debug: print("len(macro_atoms)=", len(macro_atoms))
                pD_dist = self.distanceSelectorWithCutoff.select(macro_ring_atoms, lig_atoms, cutoff=self.dist_cutoff)
                if not len(pD_dist[0]):
                    if debug: 
                        print("skipping ligand ring ", lig_rings.index(lig_ring_bnds), " vs ", end=' ')
                        print("macro ring", macro_rings.index(macro_ring_bnds))
                    continue
                #---------------------------------
                # compute the normal to macro ring
                #---------------------------------
                b1 = numpy.array(macro_atoms[0].coords)
                b2 = numpy.array(macro_atoms[2].coords)
                b3 = numpy.array(macro_atoms[4].coords)
                if debug: print("b1,b2, b3=", b1.tolist(), b2.tolist(), b3.tolist())
                # check for stacking 
                a2_1 = a2-a1
                a3_1 = a3-a1
                b2_1 = b2-b1
                b3_1 = b3-b1
                if debug: print("a2_1 = ", a2-a1)
                if debug: print("a3_1 = ", a3-a1)
                if debug: print("b2_1 = ", b2-b1)
                if debug: print("b3_1 = ", b3-b1)
                n1 = crossProduct(a3_1,a2_1) #to get the normal for the first ring
                n2 = crossProduct(b3_1,b2_1) #to get the normal for the second ring
                if debug: print("n1=", n1)
                if debug: print("n2=", n2)
                n1 = numpy.array(n1)
                n2 = numpy.array(n2)
                n1_dot_n2 = numpy.dot(n1,n2)
                if debug: print("n1_dot_n2", numpy.dot(n1,n2))
                if abs(n1_dot_n2) >= 1*tolerance: 
                    if debug: print("The rings are stacked vertically") 
                    new_result = (acbs.getAtoms(lig_ring_bnds), acbs.getAtoms(macro_ring_bnds))
                    self.results['pi_pi'].append(new_result)
                if abs(n1_dot_n2) <= 0.01*tolerance: 
                    if debug: print("The rings are stacked perpendicularly") 
                    new_result = (acbs.getAtoms(lig_ring_bnds), acbs.getAtoms(macro_ring_bnds))
                    self.results['t_shaped'].append(new_result)


    def get_pi_pi(self, print_ctr=1, comment="USER AD> "):
        #one ring parallel to another 
        #@@ UNTESTED! ... need test data
        if not len(self.results.get( 'pi_pi')):
            return ""
#        self.results['pi_pi'] = []        #stacked rings...?
        res = self.results['pi_pi']
        ss = comment + "pi_pi: %d\n"%(len(self.results['pi_pi']))
        ####
        #3l1m_lig_vs:A:HEM150:C4A,C3D,C1A,C2D,C4D,C2A,NA,ND,C3A,C1D : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:C3B,NB,C4A,C1A,C4B,NA,C2A,C2B,C3A,C1B : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:C3B,C1B,C3C,NB,C4C,NC,C2B,C1C,C4B,C2C : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:NC,C4C,C1C,C3C,C2C : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:C1A,NA,C2A,C3A,C4A : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:NB,C2B,C3B,C1B,C4B : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:NC,C4C,C1C,C3C,C2C : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:C4D,ND,C1D,C3D,C2D : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #
        # build string for  self.results['pi_pi'] 
        for res in self.results['pi_pi']:
            ss += comment + "%s~~%s\n"%(res[0].full_name(), res[1].full_name())
            ###ss += "USER %s~~%s\n"%(res[0].full_name(), res[1].full_name())
            #ss += "USER %s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
        #print "returning ss=" , ss
        return ss


    def get_t_shaped(self, print_ctr=1, comment="USER AD> "):
        #one ring perpendicular to the other
        #@@ UNTESTED! ... need test data
        if not len(self.results.get( 't_shaped')):
            return ""
        #print "in gpcr: self.results[t_shaped]=", self.results['t_shaped']
        ss = comment + "t_shaped: %d\n"%(len(self.results['t_shaped']))
        # build string for  self.results['t_shaped'] 
        for res in self.results['t_shaped']:
            #for example:
            #????
            #????
            #????
            #
            ss += comment + "%s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
        return ss


    def get_cation_pi(self, print_ctr=1, comment="USER AD> "):
        #cation near aromatic ring 
        #@@ UNTESTED! ... need test data
        if not len(self.results.get( 'cation_pi')):
            return ""
        ss = comment + "cation_pi: %d\n"%(len(self.results['cation_pi']))
        # build string for  self.results['cation_pi'] 
        for res in self.results['cation_pi']:
            #for example:
            #????
            #????
            #????
            #
            ss += comment + "%s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
        return ss


    def get_pi_cation_result(self, print_ctr=1, comment="USER AD> "):
        #aromatic ring near cation 
        #print "in gpcr: self.results[pi_cation]=", self.results['pi_cation']
        if not len(self.results.get( 'pi_cation')):
            return ""
        ss = comment + "pi_cation: %d\n"%(len(self.results['pi_cation']))
        # build string for self.results['pi_cation'] 
        for res in self.results['pi_cation']:
            #for example:
            #v=[(<Atom instance> HSG1:A:ARG8:CZ, [<Atom instance> IND: : INI 20:C1])]
            #res=(<Atom instance> HSG1:A:ARG8:CZ, [<Atom instance> IND: : INI 20:C1])
            #res[0]=<Atom instance> HSG1:A:ARG8:CZ
            #res[1]=  [<Atom instance> IND: : INI 20:C1]
            #res[1][0]=  <Atom instance> IND: : INI 20:C1
            #
            #attempt to get names for all atoms in the whole ring:
            res1_str = res[1][0].full_name()
            for rr in self.results['lig_rings']:
                ats = rr.getAtoms()
                if res[1][0] in ats:
                    res1_str = ats.full_name()
                    break
            ss += comment + "%s~~%s\n"%(res[0].full_name(), res1_str)
            #ss += "USER %s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
        return ss



    def print_ligand_residue_contacts(self, print_ctr=1):
        ctr = 1
        #pairDict is atom-based 
        #for residue-based report:
        # need to build lists of unique parents of keys 
        # and lists of unique parents of corresponding values
        res_d = {}
        for at in list(self.pairDict.keys()):
            if at.parent not in list(res_d.keys()):
                res_d[at.parent] = {}
            for close_at in self.pairDict[at]:
                res_d[at.parent][close_at.parent] = 1
        #print it out
        for lig_res in list(res_d.keys()):
            if print_ctr:
                print(ctr, lig_res.parent.name+':'+ lig_res.name + '->', end=' ')
            else:
                print(lig_res.parent.name+':'+ lig_res.name + '->', end=' ')
            for macro_res in res_d[lig_res]:
                print(macro_res.parent.name + ':' + macro_res.name + ',', end=' ')
            print()
            ctr += 1
        return res_d


    def print_macro_residue_contacts(self, print_ctr=1):
        ctr = 1
        #pairDict is atom-based 
        #for residue-based report:
        # need to build lists of unique parents of keys 
        # and lists of unique parents of corresponding values
        res_d = {}
        for at_key, at_list in list(self.pairDict.items()):
            for at in at_list:
                if at.parent not in list(res_d.keys()):
                    res_d[at.parent] = {}
                res_d[at.parent][at_key.parent] = 1
        #print it out
        for macro_res in list(res_d.keys()):
            if print_ctr:
                print(ctr, macro_res.parent.name+':'+ macro_res.name + '->', end=' ')
            else:
                print(macro_res.parent.name+':'+ macro_res.name + '->', end=' ')
            for lig_res in res_d[macro_res]:
                print(lig_res.parent.name + ':' + lig_res.name + ',', end=' ')
            print()
            ctr += 1
        return res_d

    def print_report(self, keylist=[]):
        if not len(keylist):
            keylist = [
                'lig_close_atoms',
                'lig_hb_atoms',
                'lig_hbas',
                'macro_close_atoms',
                'macro_hb_atoms',
                    ]
        d = self.results
        if self.verbose:
            for k in keylist:
                print(k, ':', len(d[k]), '-', d[k].__class__)


    def print_hb_residue(self, print_ctr=1):
        ctr = 1
        #pairDict is atom-based 
        res_d = {}
        for at in list(self.h_pairDict.keys()):
            if at.parent not in list(res_d.keys()):
                res_d[at.parent] = {}
            for close_at in self.h_pairDict[at]:
                res_d[at.parent][close_at.parent] = 1
        # print it out
        # Hbond instance are define with donAt,hAt ~ accAtt  (tilde)
        for don_res in list(res_d.keys()):
            if print_ctr:
                print(ctr, don_res.top.name+':'+don_res.parent.name+':'+ don_res.name + '->', end=' ')
            else:
                print(don_res.top.name+':'+don_res.parent.name+':'+ don_res.name + '->', end=' ')
            for acc_res in res_d[don_res]:
                print(acc_res.top.name+':'+acc_res.parent.name + ':' + acc_res.name + ',', end=' ')
            print()
            ctr += 1
        return res_d
class HydrogenBondBuilder:
    """
    object which can build hydrogen bonds between atoms according
    to their coords and atom type
    """

    def __init__(self, distCutoff=distCutoff, distCutoff2=distCutoff2,
                        d2min=d2min, d2max=d2max, d3min=d3min, d3max=d3max,
                        a2min=a2min, a2max=a2max, a3min=a3min, a3max=a3max,
                        donorTypes=allDonors, acceptorTypes=allAcceptors,
                        distOnly=False):
        d = self.paramDict = {}
        d['distCutoff'] = distCutoff
        d['distCutoff2'] = distCutoff2
        d['d2min'] = d2min
        d['d2max'] = d2max
        d['d3min'] = d3min
        d['d3max'] = d3max
        d['a2min'] = a2min
        d['a2max'] = a2max
        d['a3min'] = a3min
        d['a3max'] = a3max
        d['donorTypes'] = donorTypes
        d['acceptorTypes'] = acceptorTypes
        d['distOnly'] = distOnly
        self.distSelector = DistanceSelector(return_dist=0)


    def check_babel_types(self, ats):
        num_ats = len(ats)
        num_babel_type = len(ats.get(lambda x: hasattr(x, 'babel_type')))
        num_bnd_type = len(ats.get(lambda x: hasattr(x, 'bnd_type')))
        if (num_babel_type!=num_ats) or (num_bnd_type!=num_ats):
            babel = AtomHybridization()
            bond_orderer = BondOrder()
            tops = ats.top.uniq()
            for mol in tops: 
                babel.assignHybridization(mol.allAtoms)
                bond_orderer.assignBondOrder(mol.allAtoms, mol.allAtoms.bonds[0])
                mol.allAtoms._bndtyped = 1


    def reset(self, ats):
        tops = ats.top.uniq()
        for mol in tops:
            for a in mol.allAtoms:
                if hasattr(a, 'hbonds'):
                    for item in a.hbonds:
                        del item
                    delattr(a, 'hbonds')


    def build(self, group1, group2=None, reset=True, paramDict=None):
        """atDict <- build(group1, group2, reset, paramDict=None, **kw):
            group1: atoms 
            group2: atoms 
            reset: remove all previous hbonds, default True!

            paramDict: a dictionary with these keys and default values
            distCutoff: 2.25  hydrogen--acceptor distance
            distCutoff2: 3.00 donor... acceptor distance
            d2min: 120 <min theta for sp2 hybridized donors>
            d2max: 180 <max theta for sp2 hybridized donors>
            d3min: 120 <min theta for sp3 hybridized donors>
            d3max: 170 <max theta for sp3 hybridized donors>

            a2min: 120 <min phi for sp2 hybridized donors>
            a2max: 150 <max phi for sp2 hybridized donors>
            a3min: 100 <min phi for sp3 hybridized donors>
            a3max: 150 <max phi for sp3 hybridized donors>
            @@FIX THIS: these do not seem to be input here
            donorTypes = allDonors
            acceptorTypes = allAcceptors
           """
        #setup parameter dictionary
        if paramDict is None:
            paramDict = self.paramDict

        #setup group2 
        if group2 is None:
            group2 = group1

        #process each group
        #group1
        if group1.__class__!=Atom:
            group1 = group1.findType(Atom)
            #print "now group1=", group1.full_name()
            if not len(group1):
                return "ERROR"  #@@ OR WHAT?
        self.check_babel_types(group1)

        #group2
        if group2.__class__!=Atom:
            group2 = group2.findType(Atom)
            #print "now group2=", group2.full_name()
            if not len(group2):
                return "ERROR"  #@@ OR WHAT?
        self.check_babel_types(group2)

        if reset:
            #do optional reset: remove all prior hbonds 
            self.reset(group1)
            self.reset(group2)

        #print "group1=", len(group1)
        #print "group2=", len(group2)

        #buildHbonds
        atDict = {}
        dict1 = self.buildD(group1, paramDict)

        #@@what is this doing here???
        #sp2 hybridized atoms
        #dAts2 = ats.get(lambda x, l=sp2: x.babel_type in l)
        if group1==group2:
            #only one step:
            atD1 = self.process(dict1, dict1, paramDict)
            #print 'len(atD1).keys()=', len(atD1.keys())
            atD2 = {}
        else:
            # two steps:
            # 1: group1 donors v group2 acceptors 
            # 2: group1 acceptors vs group2 donors
            dict2 = self.buildD(group2, paramDict)
            atD1 = self.process(dict1, dict2, paramDict)
            atD2 = self.process(dict2, dict1, paramDict)
            #print 'len(atD1).keys()=', len(atD1.keys())
            #print 'len(atD2).keys()=', len(atD2.keys())
        #if called with 1 atom could get tuple of two empty dictionaries
        if type(atD1)==type(atDict):
            if len(atD1):
                atDict.update(atD1)
        if type(atD2)==type(atDict):
            if len(atD2):
                atDict.update(atD2)
        #@@DESCRIBE atDict
        #this dict has atomSets as keys and as values(?check that)
        return atDict


    def checkForPossibleH(self, ats, blen):
        #@@FIX THIS: WHAT IS THE POINT OF THIS???
        #check that if at has all bonds, at least one is to a hydrogen
        # have to do this by element??
        probAts = AtomSet(ats.get(lambda x, blen=blen: len(x.bonds)==blen))
        #probOAts = ats.get(lambda x, blen=blen: len(x.bonds)==blen)
        #probSAts = ats.get(lambda x, blen=blen: len(x.bonds)==blen)
        if probAts:
            rAts = AtomSet([])
            for at in probAts:
                if not len(at.findHydrogens()):
                    rAts.append(at)
            if len(rAts):
                ats =  ats.subtract(rAts)
        return ats
    

    def getHBDonors(self, ats, donorList):
        #getHBDonors 
        sp2 = []
        sp3 = []
        for item in sp2Donors:
            if item in donorList: sp2.append(item)
        for item in sp3Donors:
            if item in donorList: sp3.append(item)
        
        dAts2 = ats.get(lambda x, l=sp2: x.babel_type in l)
        if not dAts2: dAts2=AtomSet([])
        else: 
            dAts2 = self.checkForPossibleH(dAts2, 3)
        
        dAts3 = ats.get(lambda x, l=sp3: x.babel_type in l)
        if not dAts3: dAts3=AtomSet([])
        else: dAts3 = self.checkForPossibleH(dAts3, 4)
        return dAts2, dAts3


    def filterAcceptors(self, accAts):
        ntypes = ['Npl', 'Nam']
        npls = accAts.get(lambda x, ntypes=ntypes: x.babel_type=='Npl')
        nams = accAts.get(lambda x, ntypes=ntypes: x.babel_type=='Nam')
        #nAts = accAts.get(lambda x, ntypes=ntypes: x.babel_type in ntypes)
        restAts = accAts.get(lambda x, ntypes=ntypes: x.babel_type not in ntypes)
        if not restAts: restAts = AtomSet([])
        #if nAts:
        if npls:
            #for at in nAts:
            for at in npls:
                s = 0
                for b in at.bonds:
                    if b.bondOrder=='aromatic':
                        s = s + 2
                    else: s = s + b.bondOrder
                #if s<3:
                #apparently this is wrong
                if s<4:
                    restAts.append(at)
        if nams:
            #for at in nAts:
            for at in nams:
                s = 0
                for b in at.bonds:
                    if b.bondOrder=='aromatic':
                        s = s + 2
                    else: s = s + b.bondOrder
                    #s = s + b.bondOrder
                if s<3:
                    restAts.append(at)
        return restAts
                

    def getHBAcceptors(self, ats, acceptorList):
        #print "getHBAcceptors: acceptorList=", acceptorList
        #getHBAcceptors 
        sp2 = []
        sp3 = []
        for item in sp2Acceptors:
            if item in acceptorList: sp2.append(item)
        for item in sp3Acceptors:
            if item in acceptorList: sp3.append(item)

        dAts2 = AtomSet(ats.get(lambda x, l=sp2: x.babel_type in l))
        if dAts2: 
            dAts2 = self.filterAcceptors(dAts2)
        dAts3 = AtomSet(ats.get(lambda x, l=sp3: x.babel_type in l))
        return dAts2, dAts3
      

    def buildD(self, ats, paramDict=None):
        if paramDict is None:
            paramDict = self.paramDict
        #these are from the __call__ method of vf.buildHBonds
        if not paramDict.has_key('distCutoff'):
            paramDict['distCutoff'] = 2.25
        if not paramDict.has_key('distCutoff2'):
            paramDict['distCutoff2'] = 3.00
        if not paramDict.has_key('d2min'):
            paramDict['d2min'] = 120.
        if not paramDict.has_key('d2max'):
            paramDict['d2max'] = 180.
        if not paramDict.has_key('d3min'):
            paramDict['d3min'] = 120.
        if not paramDict.has_key('d3max'):
            paramDict['d3max'] = 170.
        if not paramDict.has_key('a2min'):
            paramDict['a2min'] = 130.
        if not paramDict.has_key('a2max'):
            paramDict['a2max'] = 170.
        if not paramDict.has_key('a3min'):
            paramDict['a3min'] = 120.
        if not paramDict.has_key('a3max'):
            paramDict['a3max'] = 170.
        if not paramDict.has_key('distOnly'):
            paramDict['distOnly'] = 0
        if not paramDict.has_key('donorTypes'):
            paramDict['donorTypes'] = allDonors
        if not paramDict.has_key('acceptorTypes'):
            paramDict['acceptorTypes'] = allAcceptors

        d = {}
        donorTypes = paramDict['donorTypes']
        donor2Ats, donor3Ats = self.getHBDonors(ats, donorTypes)
        d23 = donor2Ats + donor3Ats
        #hAts = ats.get(lambda x, d23=d23: x.element=='H' \
                    #and x.bonds[0].neighborAtom(x) in d23)
        hydrogen_atoms = ats.get(lambda x: x.element=='H' and len(x.bonds))
        #hAts = AtomSet(ats.get(lambda x, donorTypes=donorTypes: x.element=='H' \
        hAts = AtomSet(hydrogen_atoms.get(lambda x, donorTypes=donorTypes: 
                    x.bonds[0].atom1.babel_type in donorTypes\
                    or x.bonds[0].atom2.babel_type in donorTypes))
        d['hAts'] = hAts
        d['donor2Ats'] = donor2Ats
        d['donor3Ats'] = donor3Ats
    
        acceptorTypes = paramDict['acceptorTypes']
        #print "about to call getHBAcceptors with acceptorTypes=", acceptorTypes
        acceptor2Ats, acceptor3Ats = self.getHBAcceptors(ats, acceptorTypes)
        d['acceptor2Ats'] = acceptor2Ats
        d['acceptor3Ats'] = acceptor3Ats
        if acceptor2Ats:
            acceptorAts = acceptor2Ats
            if acceptor3Ats:
                acceptorAts = acceptorAts + acceptor3Ats
        elif acceptor3Ats:
            acceptorAts = acceptor3Ats
        else:
            #CHECK THIS: should it be None or AtomSet([])
            acceptorAts = None
        d['acceptorAts'] = acceptorAts
        return d
            

    def getMat(self, ats):
        pass
        #tops = ats.top.uniq()
        #if len(tops)>1: 
        #    self.warningMsg('transformation mat=None:>1 mol in atomset!')
        #    return None
        #g = tops[0].geomContainer.geoms['master']
        #return g.GetMatrix(g)

    def process(self, dict1, dict2, paramDict):
        #hAts are keys, aceptorAts are checks
        hAts = dict1['hAts']
        tAts = hAts
        dist = paramDict['distCutoff']
        distOnly = paramDict['distOnly']

        if not hAts:
            #then use donors and a different distance
            tAts = dict1['donor2Ats'] + dict1['donor3Ats']
            dist = paramDict['distCutoff2']
            
        acceptorAts = dict2['acceptorAts']
        #print "acceptorAts=", acceptorAts
        if not acceptorAts or not tAts: #6/14/2004
            return {}, {}

        #call distanceSelector on two groups of atoms with dist
        #keyMat = self.getMat(tAts)
        #checkMat = self.getMat(acceptorAts)
        atDict = self.distSelector.select(tAts, acceptorAts, dist)
        #    keyMat=keyMat, checkMat=checkMat)
        #atDict = self.distSelector.select(tAts, acceptorAts, dist)
        #first remove bonded angles
        atDict = self.removeNeighbors(atDict)

        donor2Ats = dict1['donor2Ats']
        donor3Ats = dict1['donor3Ats']
        acceptor2Ats = dict2['acceptor2Ats']
        acceptor3Ats = dict2['acceptor3Ats']

        if distOnly: 
            #need to build hbonds and return dictionary
            self.makeBonds(atDict, donor2Ats, donor3Ats, \
                    acceptor2Ats, acceptor3Ats, paramDict)
            return atDict

        badAtDict = self.filterBasedOnAngs(atDict, donor2Ats, donor3Ats, \
                    acceptor2Ats, acceptor3Ats, paramDict)
        atDict = self.removeBadAts(atDict, badAtDict)
        if atDict is None:
            atDict = {}
        return atDict


    def makeBonds(self, pD, d2Ats, d3Ats, a2Ats, a3ats, paramDict):
        for k in pD.keys():
            if k.element=='H':
                if hasattr(k, 'hbonds') and len(k.hbonds):
                    continue
                d = k.bonds[0].atom1
                if id(d)==id(k): d = k.bonds[0].atom2
                #d = k.bonds[0].neighborAtom(k)
                h = k
            else: 
                d = k
                h = None
            #pD[k] is a list of close-enough ats
            for ac in pD[k]:
                if ac==d: continue
                dSp2 = d in d2Ats
                aSp2 = ac in a2Ats
                if dSp2:
                    if aSp2: typ = 22
                    else: typ = 23
                elif aSp2: typ = 32
                else: typ = 33
                #THEY could be already bonded
                alreadyBonded = 0
                if hasattr(d, 'hbonds') and hasattr(ac,'hbonds'):
                    for hb in d.hbonds:
                        if hb.donAt==ac or hb.accAt==ac:
                            alreadyBonded = 1
                            
                if not alreadyBonded:
                    newHB = HydrogenBond(d, ac, h, typ=typ)
                    if not hasattr(ac, 'hbonds'):
                        ac.hbonds=[]
                    if not hasattr(d, 'hbonds'):
                        d.hbonds=[]
                    ac.hbonds.append(newHB)
                    d.hbonds.append(newHB)
                    if h is not None:
                        #hydrogens can have only 1 hbond
                        h.hbonds = [newHB]


    def filterBasedOnAngs(self, pD, d2Ats, d3Ats, a2Ats, a3ats, paramDict):
        badAtDict = {}
        d2max = paramDict['d2max']
        d2min = paramDict['d2min']
        d3max = paramDict['d3max']
        d3min = paramDict['d3min']
        #NEED these parameters
        a2max = paramDict['a2max']
        a2min = paramDict['a2min']
        a3max = paramDict['a3max']
        a3min = paramDict['a3min']
        #NB now pD keys could be hydrogens OR donors
        for k in pD.keys():
            if k.element=='H':
                d = k.bonds[0].atom1
                if id(d)==id(k): d = k.bonds[0].atom2
                #d = k.bonds[0].neighborAtom(k)
                h = k
            else: 
                d = k
                h = None
            badAts = AtomSet([])
            ct = 0
            for ac in pD[k]:
                if h is not None:
                    ang = getAngle(ac, h, d)
                else:
                    acN = ac.bonds[0].atom1
                    if id(acN) == id(ac): acN = ac.bonds[0].atom2
                    #acN = ac.bonds[0].neighborAtom(ac)
                    ang = getAngle(d, ac, acN)
                #print 'ang=', ang
                dSp2 = d in d2Ats
                aSp2 = ac in a2Ats
                #these limits could be adjustable
                if h is not None:
                    if dSp2:
                        upperLim = d2max
                        lowerLim = d2min
                        #upperLim = 170
                        #lowerLim = 130
                    else:
                        upperLim = d3max
                        lowerLim = d3min
                        #upperLim = 180
                        #lowerLim = 120
                else:
                    #if there is no hydrogen use d-ac-acN angles
                    if dSp2:
                        upperLim = a2max
                        lowerLim = a2min
                        #upperLim = 150
                        #lowerLim = 110
                    else:
                        upperLim = a3max
                        lowerLim = a3min
                        #upperLim = 150
                        #lowerLim = 100
                if ang>lowerLim and ang <upperLim:
                    #AT THIS MOMENT BUILD HYDROGEN BOND:
                    if dSp2:
                        if aSp2: typ = 22
                        else: typ = 23
                    elif aSp2: typ = 32
                    else: typ = 33
                    #THEY could be already bonded
                    alreadyBonded = 0
                    if hasattr(d, 'hbonds') and hasattr(ac,'hbonds'):
                        for hb in d.hbonds:
                            if hb.donAt==ac or hb.accAt==ac:
                                alreadyBonded = 1
                    if not alreadyBonded:
                        newHB = HydrogenBond(d, ac, h, theta=ang, typ=typ)
                        if not hasattr(ac, 'hbonds'):
                            ac.hbonds=[]
                        if not hasattr(d, 'hbonds'):
                            d.hbonds=[]
                        ac.hbonds.append(newHB)
                        d.hbonds.append(newHB)
                        if h is not None:
                            #hydrogens can have only 1 hbond
                            h.hbonds = [newHB]
                        #    newHB.hlen = dist
                        #else:
                        #    newHB.dlen = dist
                else:
                    badAts.append(ac)
                ct = ct + 1
            badAtDict[k] = badAts
        return badAtDict


    def removeBadAts(self, atDict, badAtDict):
        #clean-up function called after filtering on angles
        badKeys= badAtDict.keys()
        for at in atDict.keys():
            if at not in badKeys:
                continue
            if not len(badAtDict[at]):
                continue
            closeAts = atDict[at]
            badAts = badAtDict[at]
            goodAts = []
            for i in range(len(closeAts)):
                cAt = closeAts[i]
                if cAt not in badAts:
                    goodAts.append(cAt)
            if len(goodAts):
                atDict[at] = goodAts
            else:
                del atDict[at]
        return atDict


    def removeNeighbors(self, atDict):
        #filter out at-itself and at-bondedat up to 1:4
        #NB keys could be hydrogens OR donors
        for at in atDict.keys():
            closeAts = atDict[at]
            bondedAts = AtomSet([])
            for b in at.bonds:
                ###at2 = b.neighborAtom(at)
                at2 = b.atom1
                if id(at2)==id(at): at2 = b.atom2
                bondedAts.append(at2)
                #9/13 remove this:
                ##also remove 1-3
                for b2 in at2.bonds:
                    at3 = b2.atom1
                    if id(at3)==id(at2): at3 = b.atom2
                    #at3 = b2.neighborAtom(at2)
                    if id(at3)!=id(at):
                        bondedAts.append(at3)
                    #for b3 in at3.bonds:
                        #at4 = b2.neighborAtom(at3)
                        #if at4!=at and at4!=at2:
                            #bondedAts.append(at4)
            bondedAts = bondedAts.uniq()
            goodAts = []
            for i in range(len(closeAts)):
                cAt = closeAts[i]
                if cAt not in bondedAts:
                    goodAts.append(cAt)
            if len(goodAts):
                atDict[at] = goodAts
            else:
                del atDict[at]
        return atDict


    def getDonors(self, nodes, paramDict):
        donorList = paramDict['donorTypes']
        #print 'donorList=', donorList
        # currently this is a set of hydrogens
        hats = AtomSet(nodes.get(lambda x: x.element=='H'))
        #hats are optional: if none, process donors
        # if there are hats: dAts are all atoms bonded to all hydrogens
        if hats:
            dAts = AtomSet([])
            for at in hats:
                for b in at.bonds:
                    at2 = b.atom1
                    if id(at2)==id(at): at2 = b.atom2
                    dAts.append(at2)
                    #dAts.append(b.neighborAtom(at))
        else:
            dAts = nodes
        #get the sp2 hybridized possible donors which are all ns
        sp2 = []
        for t in ['Nam', 'Ng+', 'Npl']:
            if t in donorList:
                sp2.append(t)
        #ntypes = ['Nam', 'Ng+', 'Npl']

        sp2DAts = None
        if len(sp2):
            sp2DAts = AtomSet(dAts.get(lambda x, sp2=sp2: x.babel_type in sp2))

        hsp2 = AtomSet([])
        if sp2DAts:
            if hats:
                hsp2 = AtomSet(hats.get(lambda x, sp2DAts=sp2DAts:x.bonds[0].atom1 \
                        in sp2DAts or x.bonds[0].atom2 in sp2DAts))
        if sp2DAts:
            #remove any sp2 N atoms which already have 3 bonds not to hydrogens
            n2Dons = AtomSet(sp2DAts.get(lambda x: x.element=='N'))
            if n2Dons:
                n2Dons.bl=0
                for at in n2Dons:
                    for b in at.bonds:
                        if type(b.bondOrder)==type(2):
                            at.bl = at.bl + b.bondOrder
                        else:
                            at.bl = at.bl + 2
                        #allow that there might already be a hydrogen
                    nH = at.findHydrogens()
                    at.bl = at.bl - len(nH)
                badAts = AtomSet(n2Dons.get(lambda x: x.bl>2))
                if badAts:
                    sp2DAts = sp2DAts - badAts
                delattr(n2Dons,'bl')
        #get the sp3 hybridized possible donors
        sp3 = []
        for t in ['N3+', 'S3', 'O3']:
            if t in donorList:
                sp3.append(t)
        n3DAts = None
        if 'N3+' in sp3:
            n3DAts = AtomSet(dAts.get(lambda x: x.babel_type=='N3+'))
        o3DAts = None
        if 'O3' in sp3:
            o3DAts = AtomSet(dAts.get(lambda x: x.babel_type=='O3'))
        if o3DAts:
            #remove any O3 atoms which already have 2 bonds not to hydrogens
            badO3s = AtomSet([])
            for at in o3DAts:
                if len(at.bonds)<2: continue
                if len(at.findHydrogens()): continue
                else:
                    badO3s.append(at)
            if len(badO3s):
                o3DAts = o3DAts - badO3s
        s3DAts = None
        if 'S3' in sp3:
            s3DAts = AtomSet(dAts.get(lambda x: x.babel_type=='S3'))
        sp3DAts = AtomSet([])
        for item in [n3DAts, o3DAts, s3DAts]:
            if item:
                sp3DAts = sp3DAts + item
        hsp3 = AtomSet([])
        if sp3DAts:
            if hats:
                hsp3 = AtomSet(hats.get(lambda x, sp3DAts=sp3DAts:x.bonds[0].atom1 \
                    in sp3DAts or x.bonds[0].atom2 in sp3DAts))
        hsp = hsp2 + hsp3
        #print 'hsp=', hsp.name
        #print 'sp2DAts=', sp2DAts.name
        #print 'sp3DAts=', sp3DAts.name
        return hsp, sp2DAts, sp3DAts

    def getAcceptors(self, nodes, paramDict):
        acceptorList = paramDict['acceptorTypes']
        #print 'acceptorList=', acceptorList

        sp2 = []
        for t in ['Npl', 'Nam']:
            if t in acceptorList: sp2.append(t)
        n2Accs = None
        if 'Npl' in sp2:
            n2Accs = AtomSet(nodes.get(lambda x: x.babel_type=='Npl'))
        if 'Nam' in sp2:
            n2Accs2 = AtomSet(nodes.get(lambda x: x.babel_type=='Nam'))
            if n2Accs2:
                if n2Accs:
                    n2Accs = n2Accs+n2Accs2
                else:
                    n2Accs = n2Accs2
        if n2Accs is None: 
            n2Accs = AtomSet([])

        o_sp2 = []
        for t in ['O2', 'O-']:
            if t in acceptorList: sp2.append(t)

        o2Accs = None
        if 'O2' in o_sp2:
            o2Accs = AtomSet(nodes.get(lambda x: x.babel_type=='O2'))
        if 'O-' in sp2:
            o2Accs2 = AtomSet(nodes.get(lambda x: x.babel_type=='O-'))
            if o2Accs2:
                if o2Accs:
                    o2Accs = o2Accs+o2Accs2
                else:
                    o2Accs = o2Accs2
        if o2Accs is None: 
            o2Accs = AtomSet([])

        
        o3Accs = None
        if 'O3' in acceptorList:
            o3Accs = AtomSet(nodes.get(lambda x: x.babel_type=='O3'))
        if o3Accs is None: o3Accs = AtomSet([])

        s3Accs = None
        if 'S3' in acceptorList:
            s3Accs = AtomSet(nodes.get(lambda x: x.babel_type=='S3'))
        if s3Accs is None: s3Accs = AtomSet([])

        ret2Ats = AtomSet([])
        for item in [n2Accs, o2Accs]:
            ret2Ats = ret2Ats + item

        ret3Ats = AtomSet([])
        for item in [s3Accs, o3Accs]:
            ret3Ats = ret3Ats + item
        if ret2Ats: print 'ret2Ats=', ret2Ats.name
        else: print 'no ret2Ats'
        if ret3Ats: print 'ret3Ats=', ret3Ats.name
        else: print 'no ret3Ats'
        return ret2Ats, ret3Ats
class InteractionDetector:
    """
        Base class for object to detect interactions between a receptor and potential ligands... such as virtual screen results
        
        initialized with the receptor file containing entire molecule or residues of interest.
        processLigand method takes as input a pdbqt file containing docked coordinates and returns a string 
                        composed of  hbondStr + macrocloseContactStr + ligcloseContactStr
        If interections are found, a new pdbqt containing interaction description is also output: 
    """
    def __init__(self, receptor_file, percentCutoff=1., detect_pi=False, dist_cutoff=6, verbose=False, 
                        distanceSelector=None, hydrogen_bond_builder=None, distanceSelectorWithCutoff=None,
                        aromatic_cycle_bond_selector=None): 
        self.receptor_file = receptor_file
        receptor = Read(receptor_file)
        assert(len(receptor)==1)
        assert isinstance(receptor, MoleculeSet)
        self.macro = receptor[0]
        self.macro_atoms = self.macro.allAtoms
        self.macro.buildBondsByDistance()
        self.verbose = verbose
        #??useful??
        self.percentCutoff = percentCutoff
        self.detect_pi = detect_pi
        self.distanceSelector = distanceSelector 
        if self.distanceSelector is None:
            self.distanceSelector = CloserThanVDWSelector(return_dist=0)
        self.hydrogen_bond_builder = hydrogen_bond_builder 
        if self.hydrogen_bond_builder is None:
            self.hydrogen_bond_builder = HydrogenBondBuilder()
        self.distanceSelectorWithCutoff = distanceSelectorWithCutoff 
        if self.distanceSelectorWithCutoff is None:
            self.distanceSelectorWithCutoff = DistanceSelector()
        self.dist_cutoff=float(dist_cutoff)
        self.results = d = {}
        self.report_list =['lig_hb_atoms','lig_close_atoms']
        self.aromatic_cycle_bond_selector = aromatic_cycle_bond_selector
        if self.aromatic_cycle_bond_selector is None:
            self.aromatic_cycle_bond_selector = AromaticCycleBondSelector()
        if detect_pi:
            self.report_list.extend(['pi_cation','pi_pi', 'cation_pi', 't_shaped'])
        if self.verbose: print "self.report_list=", self.report_list


    def processLigand(self, ligand_file, percentCutoff=None, output=1, outputfilename=None, comment="USER  AD> ", buildHB=1, remove_modelStr=False):
        #if outputfilename is not None, have to (1) update all the strings or (2) rename ligand
        self.results = d = {}
        ligand = Read(ligand_file)
        assert isinstance(ligand, MoleculeSet)
        assert(len(ligand)==1)
        ligand = ligand[0]
        first = ligand.name.find('_model')
        last = ligand.name.rfind('_model')
        if first!=last:
            ligand.name = ligand.name[:last]
        if remove_modelStr:
            curName = ligand.name
            modelIndex=curName.find("_model")
            if modelIndex>-1:
                curName = curName[:modelIndex]
            #setup outputfilestem
            ligand.name = curName
        ligand.buildBondsByDistance()
        if not percentCutoff:
            percentCutoff = self.percentCutoff
        # first detect sets of atoms forming hydrogen bonds
        # hbondStr,hblist and has_hbonds added to process vina results which do not include valid hydrogen bonds
        hbondStr = ""
        hblist = [""]
        has_hbonds = False
        if buildHB:
            has_hbonds = True
            hbondStr = self.buildHydrogenBonds(ligand, comment=comment)           #
            if self.verbose: print "hbondStr=", hbondStr
            hblist = hbondStr.split('\n') # hbond info
            if hblist[0]=='lig_hb_atoms : 0':
                has_hbonds = False
        # next detect sets of atoms in close contact not forming hydrogen bonds
        macrocloseContactStr, ligcloseContactStr = self.buildCloseContactAtoms(percentCutoff, ligand, comment=comment)              #
        self.piResults = ""
        if self.detect_pi: 
            self.detectPiInteractions()
            if self.results['pi_cation']:
                self.piResults = self.get_pi_cation_result(print_ctr=1, comment=comment)
                if self.verbose: 
                    print "found pi_cation in ", ligand_file
                    print "set self.piResults to ", self.piResults
            if self.results['pi_pi']:
                self.piResults += self.get_pi_pi(print_ctr=1, comment=comment)
                if self.verbose: 
                    print "set self.piResults to ", self.piResults
        macro_cclist = macrocloseContactStr.split(';')
        lig_cclist = ligcloseContactStr.split(';')
        if has_hbonds or len(macro_cclist):
            fptr = open(ligand_file)
            lines = fptr.readlines()
            fptr.close()
            if outputfilename is not None:
                optr = open(outputfilename, 'w')
            else:
                optr = open(ligand_file, 'w')
            for new_l in hblist:
                if not(len(new_l)): #don't output empty lines
                    continue
                if new_l.find(comment)!=0:
                    new_l = comment + new_l
                if new_l != hblist[-1]:
                    optr.write(new_l+"\n")
                else:
                    optr.write(new_l) # no newline after the last one
            for new_l in macro_cclist:
                if not(len(new_l)): #don't output empty lines
                    continue
                if new_l.find(comment)!=0:
                    new_l = comment + new_l
                if new_l != macro_cclist[-1]:
                    optr.write(new_l + "\n")
                else:
                    optr.write(new_l)
            for new_l in lig_cclist:
                if not(len(new_l)): #don't output empty lines
                    continue
                if new_l.find(comment)!=0:
                    new_l = comment + new_l
                if new_l != lig_cclist[-1]:
                    optr.write(new_l + "\n")
                else:
                    optr.write(new_l)
            if len(self.piResults): ##???
                for s in self.piResults.split("\n"):
                    optr.write(s+"\n")
            for l in lines:
                optr.write(l)
            optr.close()
        return hbondStr + macrocloseContactStr + ligcloseContactStr
            

    def getResultStr(self, verbose=False):
        #only write important ones: hbonds, vdw_contacts, ?pi_pi,pi_cation etc?
        ss = ""
        for k in self.report_list:
            v = self.results[k]
            #key:atom1.full_name();
            if len(v):
                if verbose: print "gRS: report for ", k
                if verbose: print  "USER:  "******":" + str(len(v)) 
                if verbose: print  "     start ss= ", ss
                ss +=  k + ":" + str(len(v))  + "\n"
                if k=='lig_hb_atoms':
                    if verbose: print "@@ getResultStr lig_hb_atoms"
                    hbstr = ""
                    for lig_at in v:
                        for hb in lig_at.hbonds:
                            if hasattr(hb, 'hAt'):
                                hbstr += 'USER %s-%s~%s\n'%(hb.donAt.full_name(), hb.hAt.full_name(),hb.accAt.full_name())
                            else:
                                hbstr += 'USER %s~%s\n'%(hb.donAt.full_name(), hb.accAt.full_name())
                    if verbose: print "hbstr="
                    #add it to ss here
                    ss += hbstr
                    if verbose: print "with hbstr: ss=", ss
                #if self.verbose:
                elif k == 'pi_cation':
                    for res in v:
                        #v=[(<Atom instance> HSG1:A:ARG8:CZ, [<Atom instance> IND: : INI 20:C1])]
                        #res=(<Atom instance> HSG1:A:ARG8:CZ, [<Atom instance> IND: : INI 20:C1])
                        #res[0]=<Atom instance> HSG1:A:ARG8:CZ
                        #res[1]=  [<Atom instance> IND: : INI 20:C1]
                        #res[1][0]=  <Atom instance> IND: : INI 20:C1
                        #
                        ss += "USER %s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
                else:
                    for w in v:
                        if verbose: print "@@ getResultStr w=", w
                        try:
                            ss += 'USER ' + w.full_name()+'\n;'
                        except:
                            if verbose: print "except on ", w
                            ss += "USER " + str(w)
                ss += "\n" 
                if verbose: print  "     end ss= ", ss
        return ss


    def buildHydrogenBonds(self, ligand, comment="USER AD> "):
        h_pairDict = self.hydrogen_bond_builder.build(ligand.allAtoms, self.macro_atoms)
        self.h_pairDict = h_pairDict
        #keys should be from lig, values from macro 
        #sometimes are not...@@check this@@
        h_results = {}
        for k, v in h_pairDict.items():
            h_results[k] = 1
            for at in v:
                h_results[at] = 1
        all_hb_ats = AtomSet(h_results.keys())  #all
        d = self.results
        macro_hb_ats = d['macro_hb_atoms'] = all_hb_ats.get(lambda x: x.top==self.macro)
        self.macro_hb_ats = macro_hb_ats
        # process lig
        lig_hb_ats = d['lig_hb_atoms'] = all_hb_ats.get(lambda x: x in ligand.allAtoms)
        self.lig_hb_ats = lig_hb_ats
        outS = comment + "lig_hb_atoms : %d\n"%(len(lig_hb_ats))
        for p in self.lig_hb_ats:  #intD.results['lig_hb_atoms']:
            for hb in p.hbonds:
                if hasattr(hb, 'used'): continue
                if hb.hAt is not None:
                    outS += comment + "%s,%s~%s\n"%(hb.donAt.full_name(), hb.hAt.name, hb.accAt.full_name())
                else:
                    outS += comment + "%s~%s\n"%(hb.donAt.full_name(), hb.accAt.full_name())
                hb.used = 1
                #hsg1V:B:ARG8:NH2,HH22~clean: : INI 20:N5
                #clean: : INI 20:O4,H3~hsg1V:B:ASP29:OD2
                #clean: : INI 20:O4,H3~hsg1V:B:ASP29:OD2
                #clean: : INI 20:N4,H3~hsg1V:B:GLY27:O
                #clean: : INI 20:O2,H2~hsg1V:B:ASP25:OD1
        #macroHStr = self.macro.allAtoms.get(lambda x: hasattr(x, 'hbonds') and len(x.hbonds)).full_name()
        #ligHStr = ligand.allAtoms.get(lambda x: hasattr(x, 'hbonds') and len(x.hbonds)).full_name()
        #return  macroHStr + '==' + ligHStr
        if self.verbose: 
            print  "buildHB returning:"
            print outS
        return outS



    def buildCloseContactAtoms(self, percentCutoff, ligand, comment="USER AD> "):
        pairDict = self.distanceSelector.select(ligand.allAtoms,
                        self.macro_atoms, percentCutoff=percentCutoff)
        self.pairDict = pairDict
        #reset here
        lig_close_ats = AtomSet()
        macro_close_ats = AtomSet()
        cdict = {}
        for k,v in pairDict.items():
            if len(v):
                cdict[k] = 1
            for at in v:
                if at not in macro_close_ats:
                    cdict[at] = 1
        closeAtoms = AtomSet(cdict.keys())
        lig_close_ats = closeAtoms.get(lambda x: x.top==ligand).uniq()
        #ligClAtStr = lig_close_ats.full_name()
        ligClAtStr = comment + "lig_close_ats: %d\n" %( len(lig_close_ats))
        if len(lig_close_ats):
            ligClAtStr += comment + "%s\n" %( lig_close_ats.full_name())
        macro_close_ats = closeAtoms.get(lambda x: x in self.macro_atoms).uniq()
        macroClAtStr = comment + "macro_close_ats: %d\n" %( len(macro_close_ats))
        if len(macro_close_ats):
            macroClAtStr += comment + "%s\n" %( macro_close_ats.full_name())
        #macroClAtStr = "macro_close_ats: " + len(macro_close_ats)+"\n" +macro_close_ats.full_name()
        rdict = self.results
        rdict['lig_close_atoms'] = lig_close_ats
        rdict['macro_close_atoms'] = macro_close_ats
        if self.verbose: print "macroClAtStr=", macroClAtStr
        if self.verbose: print "ligClAtStr=", ligClAtStr
        if self.verbose: print "returning "+ macroClAtStr + '==' + ligClAtStr
        return  macroClAtStr , ligClAtStr


    def getCations(self, atoms):
        #select atoms in ARG and LYS residues
        arg_cations = atoms.get(lambda x: (x.parent.type=='ARG' and \
                                x.name in ['CZ']))
        lys_cations = atoms.get(lambda x: (x.parent.type=='LYS' and \
                                x.name in ['NZ', 'HZ1', 'HZ2', 'HZ3']))
        #select any positively-charged metal ions... cannot include CA here
        metal_cations = atoms.get(lambda x: x.name in ['Mn','MN', 'Mg',\
                                'MG', 'FE', 'Fe', 'Zn', 'ZN'])
        ca_cations = atoms.get(lambda x: x.name in ['CA', 'Ca'] and x.parent.type=='CA')
        cations = AtomSet() 
        #cations.extend(arg_cations)
        for a in arg_cations:
            cations.append(a)
        #cations.extend(lys_cations)
        for a in lys_cations:
            cations.append(a)
        #cations.extend(metal_cations)
        for a in metal_cations:
            cations.append(a)
        #cations.extend(ca_cations)
        for a in ca_cations:
            cations.append(a)
        return cations


    def detectPiInteractions(self, tolerance=0.95, debug=False, use_all_cycles=False):
        if debug: print "in detectPiInteractions"
        self.results['pi_pi'] = []        #stacked rings...?
        self.results['t_shaped'] = []     #one ring perpendicular to the other
        self.results['cation_pi'] = []    #
        self.results['pi_cation'] = []    #
        self.results['macro_cations'] = []#
        self.results['lig_cations'] = []  #
        #at this point have self.results
        if not len(self.results['lig_close_atoms']):
            return
        lig_atoms = self.results['lig_close_atoms'].parent.uniq().atoms
        macro_res = self.results['macro_close_atoms'].parent.uniq()
        if not len(macro_res):
            return
        macro_atoms = macro_res.atoms
        l_rf = RingFinder()
        #Ligand
        l_rf.findRings2(lig_atoms, lig_atoms.bonds[0])
        #rf.rings is list of dictionaries, one per ring, with keys 'bonds' and 'atoms'
        if debug: print "LIG: len(l_rf.rings)=", len(l_rf.rings)
        if not len(l_rf.rings):
            if debug: print "no lig rings found by l_rf!"
            return
        acbs = self.aromatic_cycle_bond_selector
        #acbs = AromaticCycleBondSelector()
        lig_rings = []
        for r in l_rf.rings:
            ring_bnds = r['bonds']
            if use_all_cycles: 
                lig_rings.append(ring_bnds)
            else:
                arom_bnds = acbs.select(ring_bnds)
                if len(arom_bnds)>4:
                    lig_rings.append(arom_bnds)
        if debug: print "LIG: len(lig_arom_rings)=", len(lig_rings)
        self.results['lig_rings'] = lig_rings
        self.results['lig_ring_atoms'] = AtomSet()
        #only check for pi-cation if lig_rings exist
        if len(lig_rings):
            macro_cations = self.results['macro_cations'] = self.getCations(macro_atoms)
            macro_cations = macro_cations.get(lambda x: x.element!='H')
            lig_ring_atoms = AtomSet()
            u = {}
            for r in lig_rings:
                for a in BondSet(r).getAtoms():
                    u[a] = 1
            if len(u): 
                lig_ring_atoms = AtomSet(u.keys())
                lig_ring_atoms.sort()
                self.results['lig_ring_atoms'] = lig_ring_atoms
            if len(macro_cations):
                if debug: print "check distances from lig_rings to macro_cations here"
                #macro cations->lig rings
                pairDict2 = self.distanceSelector.select(lig_ring_atoms,macro_cations)
                z = {}
                for key,v in pairDict2.items():
                    val = v.tolist()[0]
                    if val in macro_cations:
                        z[val] = [key]
                if len(z):
                    self.results['pi_cation'] = (z.items())
                else:
                    self.results['pi_cation'] = []
        #check the distance between the rings and the macro_cations
        self.results['lig_cations'] = self.getCations(lig_atoms)
        lig_cations = self.results['lig_cations'] 
        #remove hydrogens
        lig_cations = lig_cations.get(lambda x: x.element!='H')
        #Macromolecule
        m_rf = RingFinder()
        m_rf.findRings2(macro_res.atoms, macro_res.atoms.bonds[0])
        #rf.rings is list of dictionaries, one per ring, with keys 'bonds' and 'atoms'
        if debug: print "MACRO: len(m_rf.rings)=", len(m_rf.rings)
        if not len(m_rf.rings):
            if debug: print "no macro rings found by m_rf!"
            return
        macro_rings = []
        for r in m_rf.rings:
            ring_bnds = r['bonds']
            if use_all_cycles: 
                macro_rings.append(ring_bnds)
            else:
                arom_bnds = acbs.select(ring_bnds)
                if len(arom_bnds)>4:
                    macro_rings.append(arom_bnds)
        if debug: print "len(macro_arom_rings)=", len(macro_rings)
        self.results['macro_rings'] = macro_rings
        self.results['macro_ring_atoms'] = AtomSet()
        #only check for pi-cation if macro_rings exist
        if len(macro_rings):
            macro_ring_atoms = AtomSet()
            u = {}
            for r in macro_rings:
                for a in BondSet(r).getAtoms(): #new method of bondSets
                    u[a] = 1
            if len(u):
                macro_ring_atoms = AtomSet(u.keys())
                macro_ring_atoms.sort()
                self.results['macro_ring_atoms'] = macro_ring_atoms
            if len(lig_cations):
                if debug: print "check distances from macro_rings to lig_cations here"
                pairDict3 = self.distanceSelector.select(macro_ring_atoms,lig_cations)
                z = {}
                for x in pairDict3.items():
                    #lig cations->macro rings
                    z.setdefault(x[1].tolist()[0], []).append(x[0])
                if len(z):
                    self.results['cation_pi'] = (z.items())
                else:
                    self.results['cation_pi'] = []
                #macro_pi_atoms = AtomSet(pairDict3.keys())
                #l_cations = AtomSet()
                #for v in pairDict3.values():
                #    for x in v:
                #        l_cations.append(x)
                #self.results['cation_pi'] = pairDict3.items()
                #self.results['cation_pi'] = (l_cations, macro_pi_atoms)
        #check for intermol distance <6 Angstrom (J.ComputChem 29:275-279, 2009)
        #compare each lig_ring vs each macro_ring
        for lig_ring_bnds in lig_rings:
            lig_atoms = acbs.getAtoms(lig_ring_bnds)
            lig_atoms.sort()
            if debug: print "len(lig_atoms)=", len(lig_atoms)
            #---------------------------------
            # compute the normal to lig ring
            #---------------------------------
            a1 = Numeric.array(lig_atoms[0].coords)
            a2 = Numeric.array(lig_atoms[2].coords)
            a3 = Numeric.array(lig_atoms[4].coords)
            if debug: print "a1,a2, a3=", a1.tolist(), a2.tolist(), a3.tolist()
            for macro_ring_bnds in macro_rings:
                macro_atoms = acbs.getAtoms(macro_ring_bnds)
                macro_atoms.sort()
                if debug: print "len(macro_atoms)=", len(macro_atoms)
                pD_dist = self.distanceSelectorWithCutoff.select(macro_ring_atoms, lig_atoms, cutoff=self.dist_cutoff)
                if not len(pD_dist[0]):
                    if debug: 
                        print "skipping ligand ring ", lig_rings.index(lig_ring_bnds), " vs ",
                        print "macro ring", macro_rings.index(macro_ring_bnds)
                    continue
                #---------------------------------
                # compute the normal to macro ring
                #---------------------------------
                b1 = Numeric.array(macro_atoms[0].coords)
                b2 = Numeric.array(macro_atoms[2].coords)
                b3 = Numeric.array(macro_atoms[4].coords)
                if debug: print "b1,b2, b3=", b1.tolist(), b2.tolist(), b3.tolist()
                # check for stacking 
                a2_1 = a2-a1
                a3_1 = a3-a1
                b2_1 = b2-b1
                b3_1 = b3-b1
                if debug: print "a2_1 = ", a2-a1
                if debug: print "a3_1 = ", a3-a1
                if debug: print "b2_1 = ", b2-b1
                if debug: print "b3_1 = ", b3-b1
                n1 = crossProduct(a3_1,a2_1) #to get the normal for the first ring
                n2 = crossProduct(b3_1,b2_1) #to get the normal for the second ring
                if debug: print "n1=", n1
                if debug: print "n2=", n2
                n1 = Numeric.array(n1)
                n2 = Numeric.array(n2)
                n1_dot_n2 = Numeric.dot(n1,n2)
                if debug: print "n1_dot_n2", Numeric.dot(n1,n2)
                if abs(n1_dot_n2) >= 1*tolerance: 
                    if debug: print "The rings are stacked vertically" 
                    new_result = (acbs.getAtoms(lig_ring_bnds), acbs.getAtoms(macro_ring_bnds))
                    self.results['pi_pi'].append(new_result)
                if abs(n1_dot_n2) <= 0.01*tolerance: 
                    if debug: print "The rings are stacked perpendicularly" 
                    new_result = (acbs.getAtoms(lig_ring_bnds), acbs.getAtoms(macro_ring_bnds))
                    self.results['t_shaped'].append(new_result)


    def get_pi_pi(self, print_ctr=1, comment="USER AD> "):
        #one ring parallel to another 
        #@@ UNTESTED! ... need test data
        if not len(self.results.get( 'pi_pi')):
            return ""
#        self.results['pi_pi'] = []        #stacked rings...?
        res = self.results['pi_pi']
        ss = comment + "pi_pi: %d\n"%(len(self.results['pi_pi']))
        ####
        #3l1m_lig_vs:A:HEM150:C4A,C3D,C1A,C2D,C4D,C2A,NA,ND,C3A,C1D : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:C3B,NB,C4A,C1A,C4B,NA,C2A,C2B,C3A,C1B : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:C3B,C1B,C3C,NB,C4C,NC,C2B,C1C,C4B,C2C : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:NC,C4C,C1C,C3C,C2C : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:C1A,NA,C2A,C3A,C4A : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:NB,C2B,C3B,C1B,C4B : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:NC,C4C,C1C,C3C,C2C : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #3l1m_lig_vs:A:HEM150:C4D,ND,C1D,C3D,C2D : 3l1m_rec:A:PHE65:CD2,CD1,CG,CZ,CE1,CE2
        #
        # build string for  self.results['pi_pi'] 
        for res in self.results['pi_pi']:
            ss += comment + "%s~~%s\n"%(res[0].full_name(), res[1].full_name())
            ###ss += "USER %s~~%s\n"%(res[0].full_name(), res[1].full_name())
            #ss += "USER %s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
        #print "returning ss=" , ss
        return ss


    def get_t_shaped(self, print_ctr=1, comment="USER AD> "):
        #one ring perpendicular to the other
        #@@ UNTESTED! ... need test data
        if not len(self.results.get( 't_shaped')):
            return ""
        #print "in gpcr: self.results[t_shaped]=", self.results['t_shaped']
        ss = comment + "t_shaped: %d\n"%(len(self.results['t_shaped']))
        # build string for  self.results['t_shaped'] 
        for res in self.results['t_shaped']:
            #for example:
            #????
            #????
            #????
            #
            ss += comment + "%s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
        return ss


    def get_cation_pi(self, print_ctr=1, comment="USER AD> "):
        #cation near aromatic ring 
        #@@ UNTESTED! ... need test data
        if not len(self.results.get( 'cation_pi')):
            return ""
        ss = comment + "cation_pi: %d\n"%(len(self.results['cation_pi']))
        # build string for  self.results['cation_pi'] 
        for res in self.results['cation_pi']:
            #for example:
            #????
            #????
            #????
            #
            ss += comment + "%s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
        return ss


    def get_pi_cation_result(self, print_ctr=1, comment="USER AD> "):
        #aromatic ring near cation 
        #print "in gpcr: self.results[pi_cation]=", self.results['pi_cation']
        if not len(self.results.get( 'pi_cation')):
            return ""
        ss = comment + "pi_cation: %d\n"%(len(self.results['pi_cation']))
        # build string for self.results['pi_cation'] 
        for res in self.results['pi_cation']:
            #for example:
            #v=[(<Atom instance> HSG1:A:ARG8:CZ, [<Atom instance> IND: : INI 20:C1])]
            #res=(<Atom instance> HSG1:A:ARG8:CZ, [<Atom instance> IND: : INI 20:C1])
            #res[0]=<Atom instance> HSG1:A:ARG8:CZ
            #res[1]=  [<Atom instance> IND: : INI 20:C1]
            #res[1][0]=  <Atom instance> IND: : INI 20:C1
            #
            #attempt to get names for all atoms in the whole ring:
            res1_str = res[1][0].full_name()
            for rr in self.results['lig_rings']:
                ats = rr.getAtoms()
                if res[1][0] in ats:
                    res1_str = ats.full_name()
                    break
            ss += comment + "%s~~%s\n"%(res[0].full_name(), res1_str)
            #ss += "USER %s~~%s\n"%(res[0].full_name(), res[1][0].full_name())
        return ss



    def print_ligand_residue_contacts(self, print_ctr=1):
        ctr = 1
        #pairDict is atom-based 
        #for residue-based report:
        # need to build lists of unique parents of keys 
        # and lists of unique parents of corresponding values
        res_d = {}
        for at in self.pairDict.keys():
            if at.parent not in res_d.keys():
                res_d[at.parent] = {}
            for close_at in self.pairDict[at]:
                res_d[at.parent][close_at.parent] = 1
        #print it out
        for lig_res in res_d.keys():
            if print_ctr:
                print ctr, lig_res.parent.name+':'+ lig_res.name + '->',
            else:
                print lig_res.parent.name+':'+ lig_res.name + '->',
            for macro_res in res_d[lig_res]:
                print macro_res.parent.name + ':' + macro_res.name + ',',
            print
            ctr += 1
        return res_d


    def print_macro_residue_contacts(self, print_ctr=1):
        ctr = 1
        #pairDict is atom-based 
        #for residue-based report:
        # need to build lists of unique parents of keys 
        # and lists of unique parents of corresponding values
        res_d = {}
        for at_key, at_list in self.pairDict.items():
            for at in at_list:
                if at.parent not in res_d.keys():
                    res_d[at.parent] = {}
                res_d[at.parent][at_key.parent] = 1
        #print it out
        for macro_res in res_d.keys():
            if print_ctr:
                print ctr, macro_res.parent.name+':'+ macro_res.name + '->',
            else:
                print macro_res.parent.name+':'+ macro_res.name + '->',
            for lig_res in res_d[macro_res]:
                print lig_res.parent.name + ':' + lig_res.name + ',',
            print
            ctr += 1
        return res_d

    def print_report(self, keylist=[]):
        if not len(keylist):
            keylist = [
                'lig_close_atoms',
                'lig_hb_atoms',
                'lig_hbas',
                'macro_close_atoms',
                'macro_hb_atoms',
                    ]
        d = self.results
        if self.verbose:
            for k in keylist:
                print k, ':', len(d[k]), '-', d[k].__class__


    def print_hb_residue(self, print_ctr=1):
        ctr = 1
        #pairDict is atom-based 
        res_d = {}
        for at in self.h_pairDict.keys():
            if at.parent not in res_d.keys():
                res_d[at.parent] = {}
            for close_at in self.h_pairDict[at]:
                res_d[at.parent][close_at.parent] = 1
        # print it out
        # Hbond instance are define with donAt,hAt ~ accAtt  (tilde)
        for don_res in res_d.keys():
            if print_ctr:
                print ctr, don_res.top.name+':'+don_res.parent.name+':'+ don_res.name + '->',
            else:
                print don_res.top.name+':'+don_res.parent.name+':'+ don_res.name + '->',
            for acc_res in res_d[don_res]:
                print acc_res.top.name+':'+acc_res.parent.name + ':' + acc_res.name + ',',
            print
            ctr += 1
        return res_d