예제 #1
0
 def __init__(self, lig, macro, percentCutoff=1.0, detect_pi=False, dist_cutoff=6., include_metal_cations=True):
     self.lig_atoms = lig.findType(Atom)
     self.lig = self.lig_atoms[0].top
     self.macro_atoms = macro.findType(Atom)
     self.macro = self.macro_atoms[0].top
     self.percentCutoff = percentCutoff
     self.distanceSelector = CloserThanVDWSelector(return_dist=0)
     self.hydrogen_bond_builder = HydrogenBondBuilder()
     self.distanceSelectorWithCutoff = DistanceSelector()
     self.dist_cutoff=float(dist_cutoff)
     self.include_metal_cations=include_metal_cations
     self.build(detect_pi=detect_pi)
 def __init__(self, lig, macro, percentCutoff=1.0, detect_pi=False, dist_cutoff=6.):
     self.lig_atoms = lig.findType(Atom)
     self.lig = self.lig_atoms[0].top
     self.macro_atoms = macro.findType(Atom)
     self.macro = self.macro_atoms[0].top
     self.percentCutoff = percentCutoff
     self.distanceSelector = CloserThanVDWSelector(return_dist=0)
     self.hydrogen_bond_builder = HydrogenBondBuilder()
     self.build(detect_pi=detect_pi)
     self.distanceSelectorWithCutoff = DistanceSelector()
     self.dist_cutoff=float(dist_cutoff)
class InteractionDescriptor:
    """
    object which can detect atoms in close contact and build hydrogen bonds between atoms according
    to their coords and atom type for two sets 
    """

    def __init__(self, lig, macro, percentCutoff=1.0, detect_pi=False, dist_cutoff=6., include_metal_cations=True):
        self.lig_atoms = lig.findType(Atom)
        self.lig = self.lig_atoms[0].top
        self.macro_atoms = macro.findType(Atom)
        self.macro = self.macro_atoms[0].top
        self.percentCutoff = percentCutoff
        self.distanceSelector = CloserThanVDWSelector(return_dist=0)
        self.hydrogen_bond_builder = HydrogenBondBuilder()
        self.distanceSelectorWithCutoff = DistanceSelector()
        self.dist_cutoff=float(dist_cutoff)
        self.include_metal_cations=include_metal_cations
        self.build(detect_pi=detect_pi)



    def build(self, percentCutoff=None, detect_pi=False):
        if not percentCutoff:
            percentCutoff = self.percentCutoff
        # first detect sets of atoms forming hydrogen bonds
        self.buildHydrogenBonds()           #
        # detect sets of atoms in close contact 
        # and detect sets of atoms in close contact not forming hydrogen bonds
        self.buildCloseContactAtoms(percentCutoff)              #
        # detect sequences of >3 contiguous residues which have atoms in close contact
        self.buildContiguousCloseResidueSequences()
        if detect_pi:
            self.detectPiInteractions()


    def buildCloseContactAtoms(self, percentCutoff):
        pairDict = self.distanceSelector.select(self.lig_atoms, 
                        self.macro_atoms, percentCutoff=percentCutoff)
        self.pairDict = pairDict
        #reset here
        lig_close_ats = AtomSet()
        macro_close_ats = AtomSet()
        closeAtoms = AtomSet()  #both sets
        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())
        
        #macro_close_ats = closeAtoms.get(lambda x: x.top==self.macro)
        #lig_close_ats = closeAtoms.get(lambda x: x.top==self.lig)
        lig_close_ats = closeAtoms.get(lambda x: x in self.lig_atoms)
        macro_close_ats = closeAtoms.get(lambda x: x in self.macro_atoms)
        rdict = self.results
        rdict['lig_close_atoms'] = lig_close_ats
        rdict['lig_close_res'] = lig_close_ats.parent.uniq()
        rdict['lig_close_carbons'] = lig_close_ats.get(lambda x: x.element=='C')
        rdict['lig_close_non_hb'] = lig_close_ats - rdict['lig_hb_atoms']
        rdict['macro_close_atoms'] = macro_close_ats
        rdict['macro_close_res'] = ResidueSet(macro_close_ats.parent.uniq())
        rdict['macro_close_carbons'] = macro_close_ats.get(lambda x: x.element=='C')
        rdict['macro_close_non_hb'] = macro_close_ats - rdict['macro_hb_atoms']
        #deprecate this
        rdict['macro_close_only'] = macro_close_ats - rdict['macro_hb_atoms']


    def buildHydrogenBonds(self):
        self.results = d = {}
        h_pairDict = self.hydrogen_bond_builder.build(self.lig_atoms, 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
        macro_hb_ats = d['macro_hb_atoms'] = all_hb_ats.get(lambda x: x.top==self.macro)
        # process lig
        lig_hb_res = d['lig_hb_res'] = ResidueSet()
        lig_hb_sidechains = d['lig_hb_sidechains'] = AtomSet()
        lig_gly_atoms = AtomSet()
        lig_hb_ats = d['lig_hb_atoms'] = all_hb_ats.get(lambda x: x in self.lig_atoms)
        if len(lig_hb_ats):
            d['lig_hb_res'] = lig_hb_res = lig_hb_ats.parent.uniq()
            d['lig_hb_sidechains'] = lig_hb_sidechains = lig_hb_res.atoms.get('sidechain')
            #to visualize hbonding involving GLY residues which have no side chains, show backbone atoms
            lig_gly_res = d['lig_hb_gly_res'] = lig_hb_res.get(lambda x: x.type=='GLY')
            if len(lig_gly_res):
                lig_gly_atoms = lig_gly_res.atoms
        # build extended set of hbonding_atoms_to_show as lines, just in case
        lig_hbas = AtomSet(lig_hb_sidechains + lig_gly_atoms + lig_hb_ats) #all from lig
        extraAts = AtomSet()
        for at in lig_hbas:
            for b in at.bonds:
                at2 = b.atom1
                if at2==at: 
                    at2 = b.atom2
                #add it to the atomset
                if at2 not in lig_hbas:
                    extraAts.append(at2)
        if len(lig_hbas):
            for at in extraAts:
                lig_hbas.append(at)
        d['lig_hbas'] = lig_hbas
        # process macro
        macro_hb_res =  ResidueSet()
        d['macro_hb_res'] = macro_hb_res
        d['macro_hb_sidechains'] = AtomSet()
        d['macro_hb_gly_res'] = ResidueSet()
        if len(macro_hb_ats):
            macro_hb_res = macro_hb_ats.parent.uniq()
        #4. display sidechains of hbonding residues as sticksNballs
            macro_hb_sidechains = d['macro_hb_sidechains'] = macro_hb_res.atoms.get('sidechain')
            macro_hb_gly_res = d['macro_hb_gly_res'] = macro_hb_res.get(lambda x: x.type=='GLY')
        macro_hb_gly_res = ResidueSet()
        macro_hb_gly_atoms = AtomSet()
        if len(macro_hb_gly_res): 
            macro_hb_gly_atoms = macro_hb_gly_res.atoms
        d['macro_hb_gly_atoms'] = macro_hb_gly_atoms
        # build extended set of hbonding_atoms_to_show as lines
        macro_hbas = d['macro_hbas'] = AtomSet()
        if len(macro_hb_ats):
            macro_hbas = d['macro_hbas'] = AtomSet(macro_hb_sidechains + macro_hb_gly_atoms + macro_hb_ats) #all from macro
        #add atoms bonded to hb atoms to make lines displayed more reasonable
        extraAts = AtomSet()
        for at in macro_hbas:
            for b in at.bonds:
                at2 = b.atom1
                if at2==at: 
                    at2 = b.atom2
                #add it to the atomset
                if at2 not in macro_hbas:
                    extraAts.append(at2)
        if len(macro_hbas):
            for at in extraAts:
                macro_hbas.append(at)
        d['hbas_macro'] = macro_hbas


    def buildContiguousCloseResidueSequences(self):
        #7. attempt to show ribbon for contiguous residues in macromolecule
        rdict = self.results
        res = rdict['macro_close_res']
        chs = res.parent.uniq()
        ss_res = ResidueSet()
        last_ind = 0
        chain1 = 1
        #output = 0
        for c in chs:
            num_res = len(c.residues)
            if num_res <3:
                continue
            rr = res.get(lambda x: x.parent==c)
            rr.sort()
            chain1 = 0
            current_seq = ResidueSet() # contiguous residues
            current_set = ResidueSet() # all pieces in this chain
            skipped_set = ResidueSet() # hole in current contiguous piece
            if len(rr)>3:  #?? min num residues for ss:at least 3??
                #reset all
                first = c.residues.index(rr[0])
                last = c.residues.index(rr[-1])
                for r in c.residues[first:last+1]:
                    if r==c.residues[-1]:
                        if r in rr: 
                            if len(current_seq)>3:
                                current_seq.append(r)
                        if len(current_seq)>4:
                            ss_res.extend(current_seq)
                    if r not in rr:  #process hole
                        skipped_set.append(r)   # one hole ok
                        if len(skipped_set)>=2: # found second hole -> end seq
                            if len(current_seq)>4:
                                if not len(current_set):
                                    current_set = current_seq[:]
                                    current_set.sort()
                                else:
                                    current_set.extend(current_seq)
                                    current_set.sort()
                                if not len(ss_res):
                                    ss_res = current_set[:]
                                else:
                                    ss_res.extend(current_set)
                            skipped_set = ResidueSet()
                            current_seq = ResidueSet()
                    else:
                        #reset skipped_set
                        if len(skipped_set)<=1 and len(current_seq)>=1: #save RR_R 
                            current_seq.extend(skipped_set) #save hole if there is one
                            current_seq.append(r) #save this residue
                        else:  #just save it
                            current_seq.append(r)
                        skipped_set= ResidueSet()
            if len(current_seq)>4:
                for r in current_seq:
                    if r not in ss_res:
                        ss_res.append(r)
            if len(current_set)>4:
                for r in current_set:
                    if r not in ss_res:
                        ss_res.append(r)
        rdict['ss_res'] = ss_res


    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)
        # including metal_cations and calcium optional
        if self.include_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
        lig_res = self.results['lig_close_res']
        if not len(lig_res):
            return
        lig_atoms = lig_res.atoms
        macro_res = self.results['macro_close_res']
        if not len(macro_res):
            return
        macro_atoms = macro_res.atoms
        l_rf = RingFinder()
        #Ligand
        l_rf.findRings2(lig_res.atoms, lig_res.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 = 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)
            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)
        #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):
            lig_cations = self.results['lig_cations'] = self.getCations(lig_atoms)
            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 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_close_res',
                'lig_close_non_hb',
                'lig_close_carbons',
                'lig_hb_atoms',
                'lig_hb_res',
                'lig_hb_sidechains',
                'lig_hbas',
                'macro_close_atoms',
                'macro_close_res',
                'macro_close_non_hb',
                'macro_hb_atoms',
                'macro_hb_res',
                'macro_hb_sidechains',
                'macro_hbas',
                'ss_res',
                    ]
        d = self.results
        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 
        #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.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 - accAtt
        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
예제 #4
0
class InteractionDescriptor:
    """
    object which can detect atoms in close contact and build hydrogen bonds between atoms according
    to their coords and atom type for two sets 
    """

    def __init__(self, lig, macro, percentCutoff=1.0, detect_pi=False, dist_cutoff=6., include_metal_cations=True):
        self.lig_atoms = lig.findType(Atom)
        self.lig = self.lig_atoms[0].top
        self.macro_atoms = macro.findType(Atom)
        self.macro = self.macro_atoms[0].top
        self.percentCutoff = percentCutoff
        self.distanceSelector = CloserThanVDWSelector(return_dist=0)
        self.hydrogen_bond_builder = HydrogenBondBuilder()
        self.distanceSelectorWithCutoff = DistanceSelector()
        self.dist_cutoff=float(dist_cutoff)
        self.include_metal_cations=include_metal_cations
        self.build(detect_pi=detect_pi)



    def build(self, percentCutoff=None, detect_pi=False):
        if not percentCutoff:
            percentCutoff = self.percentCutoff
        # first detect sets of atoms forming hydrogen bonds
        self.buildHydrogenBonds()           #
        # detect sets of atoms in close contact 
        # and detect sets of atoms in close contact not forming hydrogen bonds
        self.buildCloseContactAtoms(percentCutoff)              #
        # detect sequences of >3 contiguous residues which have atoms in close contact
        self.buildContiguousCloseResidueSequences()
        if detect_pi:
            self.detectPiInteractions()


    def buildCloseContactAtoms(self, percentCutoff):
        pairDict = self.distanceSelector.select(self.lig_atoms, 
                        self.macro_atoms, percentCutoff=percentCutoff)
        self.pairDict = pairDict
        #reset here
        lig_close_ats = AtomSet()
        macro_close_ats = AtomSet()
        closeAtoms = AtomSet()  #both sets
        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())
        
        #macro_close_ats = closeAtoms.get(lambda x: x.top==self.macro)
        #lig_close_ats = closeAtoms.get(lambda x: x.top==self.lig)
        lig_close_ats = closeAtoms.get(lambda x: x in self.lig_atoms)
        macro_close_ats = closeAtoms.get(lambda x: x in self.macro_atoms)
        rdict = self.results
        rdict['lig_close_atoms'] = lig_close_ats
        rdict['lig_close_res'] = lig_close_ats.parent.uniq()
        rdict['lig_close_carbons'] = lig_close_ats.get(lambda x: x.element=='C')
        rdict['lig_close_non_hb'] = lig_close_ats - rdict['lig_hb_atoms']
        rdict['macro_close_atoms'] = macro_close_ats
        rdict['macro_close_res'] = ResidueSet(macro_close_ats.parent.uniq())
        rdict['macro_close_carbons'] = macro_close_ats.get(lambda x: x.element=='C')
        rdict['macro_close_non_hb'] = macro_close_ats - rdict['macro_hb_atoms']
        #deprecate this
        rdict['macro_close_only'] = macro_close_ats - rdict['macro_hb_atoms']


    def buildHydrogenBonds(self):
        self.results = d = {}
        h_pairDict = self.hydrogen_bond_builder.build(self.lig_atoms, 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
        macro_hb_ats = d['macro_hb_atoms'] = all_hb_ats.get(lambda x: x.top==self.macro)
        # process lig
        lig_hb_res = d['lig_hb_res'] = ResidueSet()
        lig_hb_sidechains = d['lig_hb_sidechains'] = AtomSet()
        lig_gly_atoms = AtomSet()
        lig_hb_ats = d['lig_hb_atoms'] = all_hb_ats.get(lambda x: x in self.lig_atoms)
        if len(lig_hb_ats):
            d['lig_hb_res'] = lig_hb_res = lig_hb_ats.parent.uniq()
            d['lig_hb_sidechains'] = lig_hb_sidechains = lig_hb_res.atoms.get('sidechain')
            #to visualize hbonding involving GLY residues which have no side chains, show backbone atoms
            lig_gly_res = d['lig_hb_gly_res'] = lig_hb_res.get(lambda x: x.type=='GLY')
            if len(lig_gly_res):
                lig_gly_atoms = lig_gly_res.atoms
        # build extended set of hbonding_atoms_to_show as lines, just in case
        lig_hbas = AtomSet(lig_hb_sidechains + lig_gly_atoms + lig_hb_ats) #all from lig
        extraAts = AtomSet()
        for at in lig_hbas:
            for b in at.bonds:
                at2 = b.atom1
                if at2==at: 
                    at2 = b.atom2
                #add it to the atomset
                if at2 not in lig_hbas:
                    extraAts.append(at2)
        if len(lig_hbas):
            for at in extraAts:
                lig_hbas.append(at)
        d['lig_hbas'] = lig_hbas
        # process macro
        macro_hb_res =  ResidueSet()
        d['macro_hb_res'] = macro_hb_res
        d['macro_hb_sidechains'] = AtomSet()
        d['macro_hb_gly_res'] = ResidueSet()
        if len(macro_hb_ats):
            macro_hb_res = macro_hb_ats.parent.uniq()
        #4. display sidechains of hbonding residues as sticksNballs
            macro_hb_sidechains = d['macro_hb_sidechains'] = macro_hb_res.atoms.get('sidechain')
            macro_hb_gly_res = d['macro_hb_gly_res'] = macro_hb_res.get(lambda x: x.type=='GLY')
        macro_hb_gly_res = ResidueSet()
        macro_hb_gly_atoms = AtomSet()
        if len(macro_hb_gly_res): 
            macro_hb_gly_atoms = macro_hb_gly_res.atoms
        d['macro_hb_gly_atoms'] = macro_hb_gly_atoms
        # build extended set of hbonding_atoms_to_show as lines
        macro_hbas = d['macro_hbas'] = AtomSet()
        if len(macro_hb_ats):
            macro_hbas = d['macro_hbas'] = AtomSet(macro_hb_sidechains + macro_hb_gly_atoms + macro_hb_ats) #all from macro
        #add atoms bonded to hb atoms to make lines displayed more reasonable
        extraAts = AtomSet()
        for at in macro_hbas:
            for b in at.bonds:
                at2 = b.atom1
                if at2==at: 
                    at2 = b.atom2
                #add it to the atomset
                if at2 not in macro_hbas:
                    extraAts.append(at2)
        if len(macro_hbas):
            for at in extraAts:
                macro_hbas.append(at)
        d['hbas_macro'] = macro_hbas


    def buildContiguousCloseResidueSequences(self):
        #7. attempt to show ribbon for contiguous residues in macromolecule
        rdict = self.results
        res = rdict['macro_close_res']
        chs = res.parent.uniq()
        ss_res = ResidueSet()
        last_ind = 0
        chain1 = 1
        #output = 0
        for c in chs:
            num_res = len(c.residues)
            if num_res <3:
                continue
            rr = res.get(lambda x: x.parent==c)
            rr.sort()
            chain1 = 0
            current_seq = ResidueSet() # contiguous residues
            current_set = ResidueSet() # all pieces in this chain
            skipped_set = ResidueSet() # hole in current contiguous piece
            if len(rr)>3:  #?? min num residues for ss:at least 3??
                #reset all
                first = c.residues.index(rr[0])
                last = c.residues.index(rr[-1])
                for r in c.residues[first:last+1]:
                    if r==c.residues[-1]:
                        if r in rr: 
                            if len(current_seq)>3:
                                current_seq.append(r)
                        if len(current_seq)>4:
                            ss_res.extend(current_seq)
                    if r not in rr:  #process hole
                        skipped_set.append(r)   # one hole ok
                        if len(skipped_set)>=2: # found second hole -> end seq
                            if len(current_seq)>4:
                                if not len(current_set):
                                    current_set = current_seq[:]
                                    current_set.sort()
                                else:
                                    current_set.extend(current_seq)
                                    current_set.sort()
                                if not len(ss_res):
                                    ss_res = current_set[:]
                                else:
                                    ss_res.extend(current_set)
                            skipped_set = ResidueSet()
                            current_seq = ResidueSet()
                    else:
                        #reset skipped_set
                        if len(skipped_set)<=1 and len(current_seq)>=1: #save RR_R 
                            current_seq.extend(skipped_set) #save hole if there is one
                            current_seq.append(r) #save this residue
                        else:  #just save it
                            current_seq.append(r)
                        skipped_set= ResidueSet()
            if len(current_seq)>4:
                for r in current_seq:
                    if r not in ss_res:
                        ss_res.append(r)
            if len(current_set)>4:
                for r in current_set:
                    if r not in ss_res:
                        ss_res.append(r)
        rdict['ss_res'] = ss_res


    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)
        # including metal_cations and calcium optional
        if self.include_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
        lig_res = self.results['lig_close_res']
        if not len(lig_res):
            return
        lig_atoms = lig_res.atoms
        macro_res = self.results['macro_close_res']
        if not len(macro_res):
            return
        macro_atoms = macro_res.atoms
        l_rf = RingFinder()
        #Ligand
        l_rf.findRings2(lig_res.atoms, lig_res.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 = 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)
            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)
        #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):
            lig_cations = self.results['lig_cations'] = self.getCations(lig_atoms)
            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 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_close_res',
                'lig_close_non_hb',
                'lig_close_carbons',
                'lig_hb_atoms',
                'lig_hb_res',
                'lig_hb_sidechains',
                'lig_hbas',
                'macro_close_atoms',
                'macro_close_res',
                'macro_close_non_hb',
                'macro_hb_atoms',
                'macro_hb_res',
                'macro_hb_sidechains',
                'macro_hbas',
                'ss_res',
                    ]
        d = self.results
        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 
        #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.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 - accAtt
        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