Esempio n. 1
0
    def limitTorsions(self, numTors, type, simpleModel=1):
        """sets number of torsions to specified number by inactivating either those which
move the fewest atoms or those which move the most. if number is > than
current but less than possible, torsions are reactivated
        """

        print('lT: numTors=', numTors, ' type=', type)
        allAts = self.allAtoms
        root = self.ROOT
        torscount = self.torscount
        print('torscount=', torscount, end=' ')
        #NB: torscount is not necessarily the max
        #ie it could have been adjusted already
        at0 = self.allAtoms[0]
        if not hasattr(self, 'torTree') or not hasattr(at0, 'tt_ind'):
            self.torTree = TorTree(self.parser, root)
        torsionMap = self.torTree.torsionMap
        torsionMapNum = len(torsionMap)
        print('len(tm)=', torsionMapNum)
        possibleTors = self.possible_torscount
        if simpleModel:
            self.setTorsions(numTors, type)
            return

        #FIX THIS: what if want to increase active torsions
        if torscount == numTors:
            msg = 'specified number==number present: no adjustment'
            return 'msg'
        elif torscount < numTors:
            if torscount == possibleTors:
                #if torscount==torsionMapNum:
                msg = 'specified number == number possible: no adjustment'
                return msg
            else:
                #in this case turn on as many as possible
                #if numTors>=torsionMapNum:
                if numTors >= possibleTors:
                    #turn on everything
                    delta = possibleTors - torscount
                    #delta = torsionMapNum - torscount
                else:
                    delta = numTors - torscount
                self.turnOnTorsions(delta, type)
        else:
            #torscount>numTors
            #in this case turn them off
            delta = torscount - numTors
            self.turnOffTorsions(delta, type)
        msg = str(delta) + ' torsions changed'
        return msg
    def limitTorsions(self, numTors, type, simpleModel=1):
        """sets number of torsions to specified number by inactivating either those which
move the fewest atoms or those which move the most. if number is > than
current but less than possible, torsions are reactivated
        """

        print 'lT: numTors=', numTors, ' type=', type
        allAts = self.allAtoms
        root = self.ROOT
        torscount = self.torscount
        print 'torscount=', torscount, 
        #NB: torscount is not necessarily the max
        #ie it could have been adjusted already
        at0 = self.allAtoms[0]
        if not hasattr(self, 'torTree') or not hasattr(at0, 'tt_ind'):
            self.torTree = TorTree(self.parser, root)
        torsionMap = self.torTree.torsionMap
        torsionMapNum = len(torsionMap)
        print 'len(tm)=', torsionMapNum
        possibleTors = self.possible_torscount
        if simpleModel:
            self.setTorsions(numTors, type)
            return 

        #FIX THIS: what if want to increase active torsions
        if torscount==numTors:
            msg = 'specified number==number present: no adjustment'
            return 'msg'
        elif torscount<numTors:
            if torscount==possibleTors:
                #if torscount==torsionMapNum:
                msg = 'specified number == number possible: no adjustment'
                return msg
            else:
                #in this case turn on as many as possible
                #if numTors>=torsionMapNum:
                if numTors>=possibleTors:
                    #turn on everything
                    delta = possibleTors - torscount
                    #delta = torsionMapNum - torscount
                else:
                    delta = numTors - torscount
                self.turnOnTorsions(delta, type)
        else:
            #torscount>numTors
            #in this case turn them off 
            delta = torscount - numTors
            self.turnOffTorsions(delta, type)
        msg = str(delta) + ' torsions changed'
        return msg
 def buildTorsionTree(self):
     if not hasattr(self, 'ROOT'):
         print 'must set ROOT first!'
         return
     self.torTree = TorTree(self.parser, self.ROOT)
class LigandMixin(Subject):
    """ lfo adds capability to a protein/molecule to be used as a ligand 
            in an AutoDock docking

    """


    def setup(self, useProteinAromaticList=1,
                    aromaticCutOff=7.5, maxtors=32,
                    autoMergeNPHS=1):
        if self.chains[0].hasBonds==0:
            self.buildBondsByDistance()
        self.useProteinAromaticList = useProteinAromaticList
        self.aromaticCutOff = aromaticCutOff
        self.maxtors = 32
        self.autoMergeNPHS = autoMergeNPHS
        self.bondClassifier = AutoDockBondClassifier()
        self.torsionTree = None
        self.message = ""
        self.torscount = 0
        self.possible_torscount = 0
        self.TORSDOF = 0
        self.resKeys = q.keys()
        self.PdbqWriter = PdbqWriter()
        #should this be here?
        msg = 'initialized ' + self.name +':\n'
        #detect whether isPeptide
        msg += self.checkIsPeptide()
        #process charges
        msg += self.checkCharges()
        ##process hydrogens
        msg += self.checkHydrogens()
        #process bonds
        #this doesn't report anything???
        newmsg, nphs = self.checkBonds()
        msg += newmsg
        #process aromaticCarbons
        msg += self.checkAromatics()
        return msg, nphs


    def checkIsPeptide(self):
        """
        checkIsPeptide
        """
        #check whether each restype is in std list
        #if so, self is a peptide
        resSet = self.chains.residues
        dict = {}
        for r in resSet:
            dict[r.type] = 0
        for t in dict.keys():
            if t not in self.resKeys:
                self.isPeptide = 0
                return '    -it is not a peptide\n'
        #only get to this point if all
        #residue types were found
        self.isPeptide = 1
        return '    -it is a peptide\n'


    def checkCharges(self):
        """
        checkCharges
        """
        msg = '     -already had charges'
        if hasattr(self, 'checked_charges'):
            return msg
        msg = ""
        needToAdd = 0
        chargedAts = self.allAtoms.get(lambda x: hasattr(x, 'charge'))
        if not chargedAts:
            needToAdd = 1
        elif len(chargedAts)!=len(self.allAtoms):
            needToAdd = 1
        elif len(filter(lambda x:x.charge==0, chargedAts))==len(chargedAts):
            #this checks that each atom doesn't have charge=0
            needToAdd = 1
        #to add Kollman need to:
        #   look up each atom's parent in q to get dict=q[at.parent.type]
        #   find the atom's name in dict to get a charge
        #   set atom._charges['Kollman'] to that charge
        #   there are special problems with his, cys and termini
        #   set allAtoms.chargeSet to 'Kollman'
        if needToAdd:     
            if not hasattr(self, 'isPeptide'):
                msg = self.checkIsPeptide()
            if self.isPeptide:
                #self.checked_charges = 'needs Kollman'
                self.addKollman()
                self.checked_charges = 'has Kollman'
                #self.vf.addKollmanCharges(self, topCommand=0, log=0)
                msg = msg + '    -added Kollman charges\n'
            else:
                #self.checked_charges = 'needs gasteiger'
                self.computeGasteiger()
                self.checked_charges = 'has gasteiger'
                #self.vf.computeGasteiger(self, topCommand=0, log=0)
                msg = msg + '    -added gasteiger charges\n'
        else:
            self.checked_charges = 'has charges'
            msg = msg + '   -already had charges\n'
        # adt will have to add charges... AND check them
        #at this point, every atom has a charge and charges are not all 0k
        #have to have unit charges per residue
        #print 'calling checkMolCharges'
        #errCharge, resList = checkMolCharges(self, self.vf)
        #FIX THIS: need to add mechanism to adjust charges
        #gui to let user pick + change
        #or either change on first atom or spread over all
        #self.checked_charges = 1
        #if added charges, msg will say which one
        #if did't add charges, msg will be 'ERROR'
        return msg


    def computeGasteiger(self):
        #to compute Gasteiger need to:
        #   create an AtomHybridization()
        #   call its assignHybridization method on self.allAtoms
        #   create a Gasteiger()
        #   call its compute method on self.allAtoms
        # THEN move gast_charge into _charges with gasteiger key
        #   set allAtoms.chargeSet to 'gasteiger'
        # THEN delattr gast_charge from allAtoms
        allAts = self.allAtoms
        ah = AtomHybridization()
        ah.assignHybridization(allAts)
        Gast = Gasteiger()
        Gast.compute(allAts)
        gastCharges = []
        for c in allAts.gast_charge:
            gastCharges.append(round(c, 3))
        allAts.addCharges('gasteiger', gastCharges)
        del allAts.gast_charge
        allAts.chargeSet = 'gasteiger'


    def addKollman(self):
        #to add Kollman need to:
        #   look up each atom's parent in q to get dict=q[at.parent.type]
        #   find the atom's name in dict to get a charge
        #   set atom._charges['Kollman'] to that charge
        #   there are special problems with his, cys and termini
        #   set allAtoms.chargeSet to 'Kollman'

        for a in self.allAtoms:
            dict = q.get(a.parent.type, {})
            if len(dict):
                a._charges['Kollman'] = dict.get(a.parent.type, 0)
            else:
                a._charges['Kollman'] = 0
            a.chargeSet = 'Kollman'


    def checkHydrogens(self):
        """
        checkHydrogens
        """
        #what if self doesn't have bonds
        if not self.chains[0].hasBonds:
            self.buildBondsByDistance()
        hs = self.allAtoms.get(lambda x: x.element=='H')
        self.nphs = AtomSet()
        if not hs:
            msg = '    -no polar hydrogens found!\n'
        if hs:
            nphs = hs.get(lambda x: x.bonds[0].atom1.element=='C' or \
                    x.bonds[0].atom2.element=='C')
            if nphs:
                self.nphs = nphs
                msg = '    -found ' + str(len(self.nphs)) + ' nonpolar hydrogens\n'
            else:
                msg = '    -no nonpolar hydrogens\n'
        return msg


    def mergeNPHS(self):
        lenNPHS = len(self.nphs)
        if not lenNPHS:
            return
        nphs = self.nphs
        atLen = len(self.allAtoms)
        self.allAtoms = self.allAtoms - nphs
        #first add charge to nph's heavy atom
        chList = nphs[0]._charges.keys()
        if not len(chList):
            print 'no charges present'
            return 'XXX'
        #check that all nphs have a certain kind of charge
        for at in nphs:
            chs = at._charges.keys()
            for c in chList:
                if c not in chs:
                    chList.remove(c)
        if len(chList):
            for chargeSet in chList:
                for h in nphs:
                    b = h.bonds[0]
                    c = b.atom1
                    if c==h: 
                        c = b.atom2
                    c._charges[chargeSet] = c._charges[chargeSet] + h._charges[chargeSet]
        #next delete nphs
        for h in nphs:
            b = h.bonds[0]
            c = b.atom1
            if c==h: 
                c = b.atom2
            c.bonds.remove(b)
            h.bonds = BondSet()
            h.parent.remove(h)
            #nb: 
            #these don't show up in deleteMol.getFreeMemoryInformation
            del h
        self.nphs = AtomSet()
        return '        and merged them\n', nphs


    def checkBonds(self):
        """
        checkBonds
        """
        msg = ""
        #print self.name, ':'
        nphs = AtomSet()
        if len(self.nphs) and self.autoMergeNPHS:
            newmsg, nphs = self.mergeNPHS()
            msg = msg + newmsg
        bc = self.bondClassifier
        bonds = self.chains.residues.atoms.bonds[0]
        for b in bonds:
            b.activeTors = 0
            b.possibleTors = 0
            b.leaf = 0
        #FIX THIS:
        #if isPeptide, don't get cycles this way
        #if self.isPeptide:
            #cycleSelector = bc['cycles']
            #del bc['cycles']
        results = bc.classify(bonds)
        #if self.isPeptide:
            #bc['cycles'] = cycleSelector
            #results['cycles'] = self.getPeptideBondDict()
        #for k,v in results.items():
        #    print k,' = ', len(v)
        self.rotatable = results['rotatable']
        self.torscount = len(self.rotatable) 
        self.possible_torscount = self.torscount
        for b in self.rotatable:
            b.activeTors = 1
            b.possibleTors = 1
        for b in results['leaf']:
            b.leaf = 1
        for b in results['cycle']:
            b.incycle = 1
        hydrogenRotators = results['hydrogenRotators']
        self.hydrogenRotators = hydrogenRotators 
        self.TORSDOF = self.torscount - len(hydrogenRotators)
        ptAts = bc.dict['rotatable'].getAtoms(self.rotatable)
        d = {}
        for a in ptAts:
            d[a] = 0
        self.pTatomset = AtomSet(d.keys())
        #print 'len(pTatomset=', len(self.pTatomset)
        self.leafbonds = results['leaf']
        self.pepbackbonds = results['ppbb']
        self.amidebonds = results['amide']
        self.cyclebonds = results['cycle']
        return msg, nphs


    def checkAromatics(self):
        """
        checkAromatics
        """
        #this depends on userPref useProteinAromaticList
        if not len(self.cyclebonds):
            self.aromaticCs = AtomSet()
            return ""
        if self.isPeptide and self.useProteinAromaticList:
            self.aromaticCs = self.getPeptideAromatics()
            return ""
        if self.isPeptide:
            self.getPeptideBondDict()
        else:
            self.getLigandBondDict()
        counter = 0
        while counter < self.cyclecount:
            counter = counter + 1
            blist = self.bondDict[counter]
            for item in blist:
                at = item.atom1
                self._getAdjAtom(item, blist)
                #now each bond has 3 atoms specified for it: its own two and the next1 to atom1
                result = self._getNormal(item)
                item.nrmsize = result[0]
                item.nrms = result[1]
                #next find the other bond w/atom2:
                z2 = filter(lambda x,item=item, at2=item.atom2, blist=blist:x!=item and x.atom1==at2 or x.atom2==at2, blist)
                #finally, return the other atom in this bond
                item.nextbond2 = z2[0]
                if item.nextbond2==item:
                    item.nextbond2 = z2[1]
                neighbor2 = self._getnxtAtom(item.atom2,item.nextbond2)
                item.next2 = neighbor2
                #next have to check whether the normals are parallel
                #check each pair in each bond, how to keep track??
                #have to get normal at item's atom2: so have to get next2 for this bond:
            #have to loop twice to make sure neighbor has nrms
            for item in blist:
                p = item.nrms
                psize = item.nrmsize
                q = item.nextbond2.nrms
                qsize = item.nextbond2.nrmsize
                #theta is the critical test for planarity:
                #if angle between 2 nrms is 0, atoms are planar
                #NB>test is comparing theta,cos(angle), w/zero
                item.theta = Numeric.dot(p,q)/(psize*qsize)
                for p in ['next1','next2','nextbond','nextbond2']:
                    delattr(item, p)
        self.updateAromatics(self.aromaticCutOff)
        msg = '    -found '+ str(len(self.aromaticCs)) + ' aromatic carbons\n'
        return  msg


    def updateAromatics(self, cutoff):
        self.aromaticCutOff = cutoff
        #cutoff = self.aromaticCutOff
        cutoffValue = math.cos(cutoff*math.pi/180.)
        aromaticCs = AtomSet([])
        atD = {}
        #to keep from overwriting names of aromatic carbons at 
        #junctions of rings use this klug
        for blist in self.bondDict.values():
            for item in blist:
                item.atom1.setThisTime = 0
                item.atom2.setThisTime = 0
        for blist in self.bondDict.values():
            ct = 0
            for item in blist:
                #these are values for the default 7.5degrees:
                #if theta>=0.997 or theta<=-0.997:
                if item.theta>=cutoffValue or item.theta<=-cutoffValue:
                    item.posAromatic = 1
                    ct = ct + 1
                else:
                    item.posAromatic = 0
            #after checking all the bonds in current cycle, compare #posAromatic w/number
            if ct==len(blist):
                #and change the name and autodock_element here....
                for b in blist:
                    at1 = b.atom1
                    at2 = b.atom2
                    if at1.element=='C':
                        at1.name = 'A' + at1.name[1:]
                        at1.autodock_element = 'A'
                        atD[at1] = 0
                        at1.setThisTime = 1
                    if at2.element=='C':
                        at2.name = 'A' + at2.name[1:]
                        at2.autodock_element = 'A'
                        atD[at2] = 0
                        at2.setThisTime = 1
                aromaticCs = AtomSet(atD.keys())
            else:
                #if there were any aromatic carbons which no longer 
                #meet the criteria, change them back
                for b in blist:
                    at1 = b.atom1
                    at2 = b.atom2
                    if at1.name[0]=='A' and not at1.setThisTime:
                        at2.autodock_element = 'C'
                        at1.name = 'C' + at1.name[1:]
                    if at2.name[0]=='A'and not at2.setThisTime:
                        at2.autodock_element = 'C'
                        at2.name = 'C' + at2.name[1:]
        #remove klug
        for blist in self.bondDict.values():
            for item in blist:
                if hasattr(item.atom1, 'setThisTime'):
                    delattr(item.atom1,'setThisTime')
                if hasattr(item.atom2, 'setThisTime'):
                    delattr(item.atom2,'setThisTime')
        for a in aromaticCs:
            a.autodock_element = 'A'
        self.aromaticCs = aromaticCs

            
    def restoreCarbons(self):
        for a in self.aromaticCs:
            if len(a.name)==1:
                a.name = 'C'
            else:
                a.name = 'C' + a.name[1:]
        a.autodock_element = 'C'

   
    def nameAromatics(self):
        for a in self.aromaticCs:
            if len(a.name)==1:
                a.name = 'A'
            else:
                a.name = 'A' + a.name[1:]
        a.autodock_element = 'A'


    def addAromatic(self, at):
        if at.element!='C':
            print at.name, ' is not a carbon'
            return 'ERROR'
        if at in self.aromaticCs:
            print at.name, ' is already in aromatic set'
            return 'ERROR'
        at.autodock_element = 'A'
        self.aromaticCs.append(at)
        print at.name, ' added to aromatic set'
   

    def removeAromatic(self, at):
        if at not in self.aromaticCs:
            print at.name, ' is not in aromatic set'
            return 'ERROR'
        self.aromaticCs.remove(at)
        at.autodock_element = 'C'
        print at.name, ' removed from aromatic set'
   

    def getPeptideAromatics(self):
        atSet = AtomSet()
        allAts = self.allAtoms
        arom_ats = allAts.get(lambda x, l = pep_aromList:\
            x.parent.type+'_'+x.name in l)
        if not arom_ats:
            return AtomSet([])
        for at in arom_ats:
            at.name = 'A' + at.name[1:]
            at.autodock_element = 'A'
        #print 'returning ', len(arom_ats), ' arom_ats'
        return arom_ats

       
    def getPeptideBondDict(self):
        resSet = self.chains.residues.get(lambda x, \
                    d = aromDict.keys():x.type in d)
        if not resSet:
            return BondSet()
        self.cyclecount = numres = len(resSet)
        #NB: cyclenum is 1 based because 0 means not numbered
        for i in range(numres):
            res = resSet[i]
            ats = res.atoms
            #bondDict keys are 1 based too
            keys = aromDict[res.type]
            bnds = bondDict[i+1] = ats.get(lambda x, \
                                keys=keys:x.name in keys).bonds[0]
            for b in bnds:
                bnds.cyclenum = i + 1
        self.bondDict = bondDict


    def getLigandBondDict(self):
        ats = self.allAtoms
        babel = AtomHybridization()
        babel.assignHybridization(self.allAtoms)
        #typeBonds???
        #typeAtoms(ats)
        rf = RingFinder()
        rf.findRings2(ats, ats.bonds[0])
        ct = 1
        bondDict = {}
        for ring in rf.rings:
            bondDict[ct] = ring['bonds']
            for b in ring['bonds']:
                b.cyclenum = ct
            ct = ct + 1
        self.cyclecount = rf.ringCount
        self.bondDict = bondDict

 
    def _numberCycle(self, at):
        for b in at.bonds:
            if hasattr(b,'incycle') and b.cyclenum==0 :
                b.cyclenum = self.cyclecount
                nxtAtom = b.atom1
                if nxtAtom==at:
                    nxtAtom = b.atom2
                self._numberCycle(nxtAtom)


    def _getnxtAtom(self, at,b):
        nxtAtom = b.atom1
        if nxtAtom==at:
            nxtAtom = b.atom2
        return nxtAtom


    def _getAdjAtom(self,b,z):
        #first get the bonds in this cycle
        at = b.atom1
        #next find the other one with at as one of the atoms:
        z2 = filter(lambda x,b=b,at=at,z=z:x!=b and x.atom1==at or x.atom2==at, z)
        #finally, return the other atom in this bond
        b.nextbond = z2[0]
        neighbor = self._getnxtAtom(at,z2[0])
        b.next1 = neighbor


    def _getNormal(self,b):
        at1 = b.next1
        at2 = b.atom1
        at3 = b.atom2
        pt1 = at1.coords
        pt2 = at2.coords
        pt3 = at3.coords
        a = Numeric.subtract(pt2,pt1)
        b = Numeric.subtract(pt3,pt2)
        p = (a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0])
        nrmsize = Numeric.sqrt(p[0]*p[0]+p[1]*p[1]+p[2]*p[2])
        return (nrmsize, p)


    def setroot(self, atom):
        """
        setroot to 'C11' or 'hsg1:A:ILE2:CA'
        """
        if type(atom)==types.StringType:
            if find(atom, ':')>-1:
                #have to use full_name list
                #check that it is an atom
                mols = ProteinSet([self])
                nodes = mols.NodesFromName(atom)
                if not nodes:
                    return 'invalid root name'
                if nodes.__class__!=AtomSet:
                    return 'invalid root name: not atom level'
            else:
                nodes = self.allAtoms.get(lambda x, n = atom:x.name==n)
            if not nodes:
                return 'invalid root name'
            if len(nodes)>1:
                return 'root name must be unique'
            atom = nodes[0]
        elif type(atom)!=types.InstanceType:
            return atom, ' invalid root atom'
        elif atom.__class__!=Atom:
            return atom, ' can only select an Atom instance as root'
        #fix this rnum0 stuff
        #in case toggling back and forth
        if hasattr(self, 'autoRoot') and hasattr(self.autoRoot, 'rnum0'):
            delattr(self.autoRoot, 'rnum0')
        #if there is an old root, remove rnum0
        if hasattr(self, 'ROOT') and hasattr(self.ROOT, 'rnum0'):
            delattr(self.ROOT, 'rnum0')
        self.ROOT = atom
        self.ROOT.rnum0 = 0
        return self.ROOT


    def autoroot(self):
        """
        autoRoot
        """
        #clear old root
        if hasattr(self, 'ROOT') and hasattr(self.ROOT, 'rnum0'):
            delattr(self.ROOT, 'rnum0')
        if hasattr(self, 'autoRoot'):
            self.ROOT = self.autoRoot
            self.ROOT.rnum0 = 0
            return
        if len(self.chains)>1:
            return  "AutoRoot not implemented for molecules with >1 chain"
        self.bestBranch = len(self.allAtoms)
        bestList = []
        for item in self.allAtoms:
            if not hasattr(item, 'bonds'): continue
            if len(item.bonds)==1 and item.bonds[0].leaf: continue
            if hasattr(item,'leaf'): continue
            item.maxbranch = 0
            for b in item.bonds:
                nxtAtom = self._getnxtAtom(item,b)
                if not b.leaf:
                    thistree = self.subTree(item,nxtAtom, self.allAtoms)
                    thisbranch = len(thistree)
                    if thisbranch>item.maxbranch:
                        item.maxbranch = thisbranch
            #bestList holds all current best choices for Root..
            if item.maxbranch<self.bestBranch:
                bestList = []
                bestList.append(item)
                self.bestBranch = item.maxbranch
            if item.maxbranch==self.bestBranch and item not in bestList:
                bestList.append(item)
        if len(bestList)>1:
            foundCycle = 0
            for at in bestList:
                at.cycleatom = 0
                for b in at.bonds:
                    #4/29: peptides won't have aromatic 
                    #but will have incycle
                    #if b.bondOrder=='aromatic':
                    if hasattr(b,'incycle'):
                        at.cycleatom = 1
                        continue
            for at in bestList:
                if at.cycleatom:
                    self.ROOT = at
                    self.autoRoot = at
                    self.ROOT.rnum0 =0
                    foundCycle = 1
                    break
            #if bestList had a cycle atom, it's been set to root..if NOT:
            if not foundCycle:
                self.autoRoot = bestList[0]
                self.ROOT = bestList[0]
                self.ROOT.rnum0 =0
        #no ties for possible root, use bestRoot...
        else:
            self.autoRoot = bestList[0]
            self.ROOT = bestList[0]
            self.ROOT.rnum0 =0
        return self.ROOT


    def buildTorsionTree(self):
        if not hasattr(self, 'ROOT'):
            print 'must set ROOT first!'
            return
        self.torTree = TorTree(self.parser, self.ROOT)


    def turnOnTorsion(self, bnd):
        """
        turnOnTorsion 
        """
        if not bnd in self.rotatable:
            return
        #this is redundant (??)
        if not bnd.possibleTors:
            return
        if not bnd.activeTors:
            self.torscount = self.torscount + 1
            bnd.activeTors = 1
            self.torTree.addTorsion(bnd.atom1.number, bnd.atom2.number)


    def turnOffTorsion(self, bnd):
        """
        turnOffTorsion 
        """
        if not bnd in self.rotatable:
            return
        #this is redundant (??)
        if not bnd.possibleTors:
            return
        if bnd.activeTors:
            self.torscount = self.torscount - 1
            bnd.activeTors = 0
            self.torTree.removeTorsion(bnd.atom1.number, bnd.atom2.number)


    def limitTorsions(self, numTors, type, simpleModel=1):
        """sets number of torsions to specified number by inactivating either those which
move the fewest atoms or those which move the most. if number is > than
current but less than possible, torsions are reactivated
        """

        print 'lT: numTors=', numTors, ' type=', type
        allAts = self.allAtoms
        root = self.ROOT
        torscount = self.torscount
        print 'torscount=', torscount, 
        #NB: torscount is not necessarily the max
        #ie it could have been adjusted already
        at0 = self.allAtoms[0]
        if not hasattr(self, 'torTree') or not hasattr(at0, 'tt_ind'):
            self.torTree = TorTree(self.parser, root)
        torsionMap = self.torTree.torsionMap
        torsionMapNum = len(torsionMap)
        print 'len(tm)=', torsionMapNum
        possibleTors = self.possible_torscount
        if simpleModel:
            self.setTorsions(numTors, type)
            return 

        #FIX THIS: what if want to increase active torsions
        if torscount==numTors:
            msg = 'specified number==number present: no adjustment'
            return 'msg'
        elif torscount<numTors:
            if torscount==possibleTors:
                #if torscount==torsionMapNum:
                msg = 'specified number == number possible: no adjustment'
                return msg
            else:
                #in this case turn on as many as possible
                #if numTors>=torsionMapNum:
                if numTors>=possibleTors:
                    #turn on everything
                    delta = possibleTors - torscount
                    #delta = torsionMapNum - torscount
                else:
                    delta = numTors - torscount
                self.turnOnTorsions(delta, type)
        else:
            #torscount>numTors
            #in this case turn them off 
            delta = torscount - numTors
            self.turnOffTorsions(delta, type)
        msg = str(delta) + ' torsions changed'
        return msg


    def setTorsions(self, numTors, type):
        #this assumes atoms have tt_ind field
        tNum = len(self.torTree.base_torsionMap)
        msg = ""
        if numTors>tNum:
            msg = 'too many torsions specified! '+ str(numTors)+  ' reducing to'+str(tNum)
            numTors = tNum
        if type=='fewest':
            rangeList = range(numTors)
        else:
            rangeList = []
            for k in range(1, numTors+1):
                rangeList.append(-k)
        #turn them all off
        baseTorsionMap = self.torTree.base_torsionMap
        torsionMap = self.torTree.torsionMap
        #turn all active nodes off
        active_nodes = torsionMap[:]
        for node in active_nodes:
            #print 'turning off node ', node.number
            self.torTree.removeTorsion(node.bond[0], node.bond[1])
            b = self.allAtoms.get(lambda x, node=node: x.tt_ind in node.bond).bonds[0][0]
            b.activeTors = 0

        #turn on the right number at correct end
        for i in rangeList:
            node = baseTorsionMap[i]
            #print '2:turning on node ', node.number
            self.torTree.addTorsion(node.bond[0], node.bond[1])
            b = self.allAtoms.get(lambda x, node=node: x.tt_ind in node.bond).bonds[0][0]
            b.activeTors = 1
            #print 'now len(torsionMap)=', len(torsionMap)
        self.torscount = numTors
        return msg


    def turnOnTorsions(self, delta, type = 'fewest'):
        allAts = self.allAtoms
        torsionMap = self.torTree.torsionMap
        baseTorsionMap = self.torTree.base_torsionMap
        torscount = self.torscount
        #turn on delta torsions + adjust torscount in dict 
        if type=='fewest':
            rangeList = range(delta)
        else:
            rangeList = []
            for k in range(1, delta+1):
                rangeList.append(-k)
        #FIX THIS: probably doesn't work
        for i in rangeList:
            node = baseTorsionMap[i]
            b = allAts.get(lambda x, node=node: x.tt_ind in node.bond).bonds[0][0]
            if not b.activeTors:
                self.torTree.addTorsion(node.bond[0], node.bond[1])
                b.activeTors = 1
            else:
                lastInd = rangeList[-1]
                if type=='fewest':
                    rangeList.append(lastInd+1)
                else:
                    rangeList.append(lastInd-1)
        #torscount should be torscount + delta here
        self.torscount = torscount + delta

        
    def turnOffTorsions(self, delta, type = 'fewest'):
        allAts = self.allAtoms
        torsionMap = self.torTree.torsionMap
        baseTorsionMap = self.torTree.base_torsionMap
        torscount = self.torscount
        #turn on delta torsions + adjust torscount in dict 
        if type=='fewest':
            rangeList = range(delta)
        else:
            rangeList = []
            for k in range(1, delta+1):
                rangeList.append(-k)
        for i in rangeList:
            node = baseTorsionMap[i]
            if node.bond==(None,None):
                print 'error in turnOff torsions with ', rangeList
                break
            b = allAts.get(lambda x, node=node: x.tt_ind in node.bond).bonds[0][0]
            if b.activeTors:
                self.torTree.removeTorsion(node.bond[0], node.bond[1])
                b.activeTors = 0
            else:
                lastInd = rangeList[-1]
                if type=='fewest':
                    rangeList.append(lastInd+1)
                else:
                    rangeList.append(lastInd-1)
        self.torscount = torscount - delta


    def setCarbonName(self, at):
        """
        setCarbonName 
        """
        if not at.element=='C':
            return
        if len(at.name)==1:
            at.name = 'C'
        else:
            at.name = 'C' + at.name[1:]



    def setAromaticName(self, at):
        """
        setAromaticName 
        """
        if not at.element=='C':
            return
        if len(at.name)==1:
            at.name = 'A'
        else:
            at.name = 'A' + at.name[1:]
Esempio n. 5
0
 def buildTorsionTree(self):
     if not hasattr(self, 'ROOT'):
         print('must set ROOT first!')
         return
     self.torTree = TorTree(self.parser, self.ROOT)
Esempio n. 6
0
class LigandMixin(Subject):
    """ lfo adds capability to a protein/molecule to be used as a ligand 
            in an AutoDock docking

    """
    def setup(self,
              useProteinAromaticList=1,
              aromaticCutOff=7.5,
              maxtors=32,
              autoMergeNPHS=1):
        if self.chains[0].hasBonds == 0:
            self.buildBondsByDistance()
        self.useProteinAromaticList = useProteinAromaticList
        self.aromaticCutOff = aromaticCutOff
        self.maxtors = 32
        self.autoMergeNPHS = autoMergeNPHS
        self.bondClassifier = AutoDockBondClassifier()
        self.torsionTree = None
        self.message = ""
        self.torscount = 0
        self.possible_torscount = 0
        self.TORSDOF = 0
        self.resKeys = list(q.keys())
        self.PdbqWriter = PdbqWriter()
        #should this be here?
        msg = 'initialized ' + self.name + ':\n'
        #detect whether isPeptide
        msg += self.checkIsPeptide()
        #process charges
        msg += self.checkCharges()
        ##process hydrogens
        msg += self.checkHydrogens()
        #process bonds
        #this doesn't report anything???
        newmsg, nphs = self.checkBonds()
        msg += newmsg
        #process aromaticCarbons
        msg += self.checkAromatics()
        return msg, nphs

    def checkIsPeptide(self):
        """
        checkIsPeptide
        """
        #check whether each restype is in std list
        #if so, self is a peptide
        resSet = self.chains.residues
        dict = {}
        for r in resSet:
            dict[r.type] = 0
        for t in list(dict.keys()):
            if t not in self.resKeys:
                self.isPeptide = 0
                return '    -it is not a peptide\n'
        #only get to this point if all
        #residue types were found
        self.isPeptide = 1
        return '    -it is a peptide\n'

    def checkCharges(self):
        """
        checkCharges
        """
        msg = '     -already had charges'
        if hasattr(self, 'checked_charges'):
            return msg
        msg = ""
        needToAdd = 0
        chargedAts = self.allAtoms.get(lambda x: hasattr(x, 'charge'))
        if not chargedAts:
            needToAdd = 1
        elif len(chargedAts) != len(self.allAtoms):
            needToAdd = 1
        elif len([x for x in chargedAts if x.charge == 0]) == len(chargedAts):
            #this checks that each atom doesn't have charge=0
            needToAdd = 1
        #to add Kollman need to:
        #   look up each atom's parent in q to get dict=q[at.parent.type]
        #   find the atom's name in dict to get a charge
        #   set atom._charges['Kollman'] to that charge
        #   there are special problems with his, cys and termini
        #   set allAtoms.chargeSet to 'Kollman'
        if needToAdd:
            if not hasattr(self, 'isPeptide'):
                msg = self.checkIsPeptide()
            if self.isPeptide:
                #self.checked_charges = 'needs Kollman'
                self.addKollman()
                self.checked_charges = 'has Kollman'
                #self.vf.addKollmanCharges(self, topCommand=0, log=0)
                msg = msg + '    -added Kollman charges\n'
            else:
                #self.checked_charges = 'needs gasteiger'
                self.computeGasteiger()
                self.checked_charges = 'has gasteiger'
                #self.vf.computeGasteiger(self, topCommand=0, log=0)
                msg = msg + '    -added gasteiger charges\n'
        else:
            self.checked_charges = 'has charges'
            msg = msg + '   -already had charges\n'
        # adt will have to add charges... AND check them
        #at this point, every atom has a charge and charges are not all 0k
        #have to have unit charges per residue
        #print 'calling checkMolCharges'
        #errCharge, resList = checkMolCharges(self, self.vf)
        #FIX THIS: need to add mechanism to adjust charges
        #gui to let user pick + change
        #or either change on first atom or spread over all
        #self.checked_charges = 1
        #if added charges, msg will say which one
        #if did't add charges, msg will be 'ERROR'
        return msg

    def computeGasteiger(self):
        #to compute Gasteiger need to:
        #   create an AtomHybridization()
        #   call its assignHybridization method on self.allAtoms
        #   create a Gasteiger()
        #   call its compute method on self.allAtoms
        # THEN move gast_charge into _charges with gasteiger key
        #   set allAtoms.chargeSet to 'gasteiger'
        # THEN delattr gast_charge from allAtoms
        allAts = self.allAtoms
        ah = AtomHybridization()
        ah.assignHybridization(allAts)
        Gast = Gasteiger()
        Gast.compute(allAts)
        gastCharges = []
        for c in allAts.gast_charge:
            gastCharges.append(round(c, 3))
        allAts.addCharges('gasteiger', gastCharges)
        del allAts.gast_charge
        allAts.chargeSet = 'gasteiger'

    def addKollman(self):
        #to add Kollman need to:
        #   look up each atom's parent in q to get dict=q[at.parent.type]
        #   find the atom's name in dict to get a charge
        #   set atom._charges['Kollman'] to that charge
        #   there are special problems with his, cys and termini
        #   set allAtoms.chargeSet to 'Kollman'

        for a in self.allAtoms:
            dict = q.get(a.parent.type, {})
            if len(dict):
                a._charges['Kollman'] = dict.get(a.parent.type, 0)
            else:
                a._charges['Kollman'] = 0
            a.chargeSet = 'Kollman'

    def checkHydrogens(self):
        """
        checkHydrogens
        """
        #what if self doesn't have bonds
        if not self.chains[0].hasBonds:
            self.buildBondsByDistance()
        hs = self.allAtoms.get(lambda x: x.element == 'H')
        self.nphs = AtomSet()
        if not hs:
            msg = '    -no polar hydrogens found!\n'
        if hs:
            nphs = hs.get(lambda x: x.bonds[0].atom1.element=='C' or \
                    x.bonds[0].atom2.element=='C')
            if nphs:
                self.nphs = nphs
                msg = '    -found ' + str(len(
                    self.nphs)) + ' nonpolar hydrogens\n'
            else:
                msg = '    -no nonpolar hydrogens\n'
        return msg

    def mergeNPHS(self):
        lenNPHS = len(self.nphs)
        if not lenNPHS:
            return
        nphs = self.nphs
        atLen = len(self.allAtoms)
        self.allAtoms = self.allAtoms - nphs
        #first add charge to nph's heavy atom
        chList = list(nphs[0]._charges.keys())
        if not len(chList):
            print('no charges present')
            return 'XXX'
        #check that all nphs have a certain kind of charge
        for at in nphs:
            chs = list(at._charges.keys())
            for c in chList:
                if c not in chs:
                    chList.remove(c)
        if len(chList):
            for chargeSet in chList:
                for h in nphs:
                    b = h.bonds[0]
                    c = b.atom1
                    if c == h:
                        c = b.atom2
                    c._charges[chargeSet] = c._charges[chargeSet] + h._charges[
                        chargeSet]
        #next delete nphs
        for h in nphs:
            b = h.bonds[0]
            c = b.atom1
            if c == h:
                c = b.atom2
            c.bonds.remove(b)
            h.bonds = BondSet()
            h.parent.remove(h)
            #nb:
            #these don't show up in deleteMol.getFreeMemoryInformation
            del h
        self.nphs = AtomSet()
        return '        and merged them\n', nphs

    def checkBonds(self):
        """
        checkBonds
        """
        msg = ""
        #print self.name, ':'
        nphs = AtomSet()
        if len(self.nphs) and self.autoMergeNPHS:
            newmsg, nphs = self.mergeNPHS()
            msg = msg + newmsg
        bc = self.bondClassifier
        bonds = self.chains.residues.atoms.bonds[0]
        for b in bonds:
            b.activeTors = 0
            b.possibleTors = 0
            b.leaf = 0
        #FIX THIS:
        #if isPeptide, don't get cycles this way
        #if self.isPeptide:
        #cycleSelector = bc['cycles']
        #del bc['cycles']
        results = bc.classify(bonds)
        #if self.isPeptide:
        #bc['cycles'] = cycleSelector
        #results['cycles'] = self.getPeptideBondDict()
        #for k,v in results.items():
        #    print k,' = ', len(v)
        self.rotatable = results['rotatable']
        self.torscount = len(self.rotatable)
        self.possible_torscount = self.torscount
        for b in self.rotatable:
            b.activeTors = 1
            b.possibleTors = 1
        for b in results['leaf']:
            b.leaf = 1
        for b in results['cycle']:
            b.incycle = 1
        hydrogenRotators = results['hydrogenRotators']
        self.hydrogenRotators = hydrogenRotators
        self.TORSDOF = self.torscount - len(hydrogenRotators)
        ptAts = bc.dict['rotatable'].getAtoms(self.rotatable)
        d = {}
        for a in ptAts:
            d[a] = 0
        self.pTatomset = AtomSet(list(d.keys()))
        #print 'len(pTatomset=', len(self.pTatomset)
        self.leafbonds = results['leaf']
        self.pepbackbonds = results['ppbb']
        self.amidebonds = results['amide']
        self.cyclebonds = results['cycle']
        return msg, nphs

    def checkAromatics(self):
        """
        checkAromatics
        """
        #this depends on userPref useProteinAromaticList
        if not len(self.cyclebonds):
            self.aromaticCs = AtomSet()
            return ""
        if self.isPeptide and self.useProteinAromaticList:
            self.aromaticCs = self.getPeptideAromatics()
            return ""
        if self.isPeptide:
            self.getPeptideBondDict()
        else:
            self.getLigandBondDict()
        counter = 0
        while counter < self.cyclecount:
            counter = counter + 1
            blist = self.bondDict[counter]
            for item in blist:
                at = item.atom1
                self._getAdjAtom(item, blist)
                #now each bond has 3 atoms specified for it: its own two and the next1 to atom1
                result = self._getNormal(item)
                item.nrmsize = result[0]
                item.nrms = result[1]
                #next find the other bond w/atom2:
                z2 = list(
                    filter(lambda x, item=item, at2=item.atom2, blist=blist: x
                           != item and x.atom1 == at2 or x.atom2 == at2,
                           blist))
                #finally, return the other atom in this bond
                item.nextbond2 = z2[0]
                if item.nextbond2 == item:
                    item.nextbond2 = z2[1]
                neighbor2 = self._getnxtAtom(item.atom2, item.nextbond2)
                item.next2 = neighbor2
                #next have to check whether the normals are parallel
                #check each pair in each bond, how to keep track??
                #have to get normal at item's atom2: so have to get next2 for this bond:
            #have to loop twice to make sure neighbor has nrms
            for item in blist:
                p = item.nrms
                psize = item.nrmsize
                q = item.nextbond2.nrms
                qsize = item.nextbond2.nrmsize
                #theta is the critical test for planarity:
                #if angle between 2 nrms is 0, atoms are planar
                #NB>test is comparing theta,cos(angle), w/zero
                item.theta = numpy.dot(p, q) / (psize * qsize)
                for p in ['next1', 'next2', 'nextbond', 'nextbond2']:
                    delattr(item, p)
        self.updateAromatics(self.aromaticCutOff)
        msg = '    -found ' + str(len(self.aromaticCs)) + ' aromatic carbons\n'
        return msg

    def updateAromatics(self, cutoff):
        self.aromaticCutOff = cutoff
        #cutoff = self.aromaticCutOff
        cutoffValue = math.cos(cutoff * math.pi / 180.)
        aromaticCs = AtomSet([])
        atD = {}
        #to keep from overwriting names of aromatic carbons at
        #junctions of rings use this klug
        for blist in list(self.bondDict.values()):
            for item in blist:
                item.atom1.setThisTime = 0
                item.atom2.setThisTime = 0
        for blist in list(self.bondDict.values()):
            ct = 0
            for item in blist:
                #these are values for the default 7.5degrees:
                #if theta>=0.997 or theta<=-0.997:
                if item.theta >= cutoffValue or item.theta <= -cutoffValue:
                    item.posAromatic = 1
                    ct = ct + 1
                else:
                    item.posAromatic = 0
            #after checking all the bonds in current cycle, compare #posAromatic w/number
            if ct == len(blist):
                #and change the name and autodock_element here....
                for b in blist:
                    at1 = b.atom1
                    at2 = b.atom2
                    if at1.element == 'C':
                        at1.name = 'A' + at1.name[1:]
                        at1.autodock_element = 'A'
                        atD[at1] = 0
                        at1.setThisTime = 1
                    if at2.element == 'C':
                        at2.name = 'A' + at2.name[1:]
                        at2.autodock_element = 'A'
                        atD[at2] = 0
                        at2.setThisTime = 1
                aromaticCs = AtomSet(list(atD.keys()))
            else:
                #if there were any aromatic carbons which no longer
                #meet the criteria, change them back
                for b in blist:
                    at1 = b.atom1
                    at2 = b.atom2
                    if at1.name[0] == 'A' and not at1.setThisTime:
                        at2.autodock_element = 'C'
                        at1.name = 'C' + at1.name[1:]
                    if at2.name[0] == 'A' and not at2.setThisTime:
                        at2.autodock_element = 'C'
                        at2.name = 'C' + at2.name[1:]
        #remove klug
        for blist in list(self.bondDict.values()):
            for item in blist:
                if hasattr(item.atom1, 'setThisTime'):
                    delattr(item.atom1, 'setThisTime')
                if hasattr(item.atom2, 'setThisTime'):
                    delattr(item.atom2, 'setThisTime')
        for a in aromaticCs:
            a.autodock_element = 'A'
        self.aromaticCs = aromaticCs

    def restoreCarbons(self):
        for a in self.aromaticCs:
            if len(a.name) == 1:
                a.name = 'C'
            else:
                a.name = 'C' + a.name[1:]
        a.autodock_element = 'C'

    def nameAromatics(self):
        for a in self.aromaticCs:
            if len(a.name) == 1:
                a.name = 'A'
            else:
                a.name = 'A' + a.name[1:]
        a.autodock_element = 'A'

    def addAromatic(self, at):
        if at.element != 'C':
            print(at.name, ' is not a carbon')
            return 'ERROR'
        if at in self.aromaticCs:
            print(at.name, ' is already in aromatic set')
            return 'ERROR'
        at.autodock_element = 'A'
        self.aromaticCs.append(at)
        print(at.name, ' added to aromatic set')

    def removeAromatic(self, at):
        if at not in self.aromaticCs:
            print(at.name, ' is not in aromatic set')
            return 'ERROR'
        self.aromaticCs.remove(at)
        at.autodock_element = 'C'
        print(at.name, ' removed from aromatic set')

    def getPeptideAromatics(self):
        atSet = AtomSet()
        allAts = self.allAtoms
        arom_ats = allAts.get(lambda x, l = pep_aromList:\
            x.parent.type+'_'+x.name in l)
        if not arom_ats:
            return AtomSet([])
        for at in arom_ats:
            at.name = 'A' + at.name[1:]
            at.autodock_element = 'A'
        #print 'returning ', len(arom_ats), ' arom_ats'
        return arom_ats

    def getPeptideBondDict(self):
        resSet = self.chains.residues.get(lambda x, \
                    d = list(aromDict.keys()):x.type in d)
        if not resSet:
            return BondSet()
        self.cyclecount = numres = len(resSet)
        #NB: cyclenum is 1 based because 0 means not numbered
        for i in range(numres):
            res = resSet[i]
            ats = res.atoms
            #bondDict keys are 1 based too
            keys = aromDict[res.type]
            bnds = bondDict[i+1] = ats.get(lambda x, \
                                keys=keys:x.name in keys).bonds[0]
            for b in bnds:
                bnds.cyclenum = i + 1
        self.bondDict = bondDict

    def getLigandBondDict(self):
        ats = self.allAtoms
        babel = AtomHybridization()
        babel.assignHybridization(self.allAtoms)
        #typeBonds???
        #typeAtoms(ats)
        rf = RingFinder()
        rf.findRings2(ats, ats.bonds[0])
        ct = 1
        bondDict = {}
        for ring in rf.rings:
            bondDict[ct] = ring['bonds']
            for b in ring['bonds']:
                b.cyclenum = ct
            ct = ct + 1
        self.cyclecount = rf.ringCount
        self.bondDict = bondDict

    def _numberCycle(self, at):
        for b in at.bonds:
            if hasattr(b, 'incycle') and b.cyclenum == 0:
                b.cyclenum = self.cyclecount
                nxtAtom = b.atom1
                if nxtAtom == at:
                    nxtAtom = b.atom2
                self._numberCycle(nxtAtom)

    def _getnxtAtom(self, at, b):
        nxtAtom = b.atom1
        if nxtAtom == at:
            nxtAtom = b.atom2
        return nxtAtom

    def _getAdjAtom(self, b, z):
        #first get the bonds in this cycle
        at = b.atom1
        #next find the other one with at as one of the atoms:
        z2 = list(
            filter(lambda x, b=b, at=at, z=z: x != b and x.atom1 == at or x.
                   atom2 == at,
                   z))
        #finally, return the other atom in this bond
        b.nextbond = z2[0]
        neighbor = self._getnxtAtom(at, z2[0])
        b.next1 = neighbor

    def _getNormal(self, b):
        at1 = b.next1
        at2 = b.atom1
        at3 = b.atom2
        pt1 = at1.coords
        pt2 = at2.coords
        pt3 = at3.coords
        a = numpy.subtract(pt2, pt1)
        b = numpy.subtract(pt3, pt2)
        p = (a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2],
             a[0] * b[1] - a[1] * b[0])
        nrmsize = numpy.sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2])
        return (nrmsize, p)

    def setroot(self, atom):
        """
        setroot to 'C11' or 'hsg1:A:ILE2:CA'
        """
        if type(atom) == bytes:
            if find(atom, ':') > -1:
                #have to use full_name list
                #check that it is an atom
                mols = ProteinSet([self])
                nodes = mols.NodesFromName(atom)
                if not nodes:
                    return 'invalid root name'
                if nodes.__class__ != AtomSet:
                    return 'invalid root name: not atom level'
            else:
                nodes = self.allAtoms.get(lambda x, n=atom: x.name == n)
            if not nodes:
                return 'invalid root name'
            if len(nodes) > 1:
                return 'root name must be unique'
            atom = nodes[0]
        #elif type(atom)!=types.InstanceType:
        elif isInstance(atom) is False:
            return atom, ' invalid root atom'
        elif atom.__class__ != Atom:
            return atom, ' can only select an Atom instance as root'
        #fix this rnum0 stuff
        #in case toggling back and forth
        if hasattr(self, 'autoRoot') and hasattr(self.autoRoot, 'rnum0'):
            delattr(self.autoRoot, 'rnum0')
        #if there is an old root, remove rnum0
        if hasattr(self, 'ROOT') and hasattr(self.ROOT, 'rnum0'):
            delattr(self.ROOT, 'rnum0')
        self.ROOT = atom
        self.ROOT.rnum0 = 0
        return self.ROOT

    def autoroot(self):
        """
        autoRoot
        """
        #clear old root
        if hasattr(self, 'ROOT') and hasattr(self.ROOT, 'rnum0'):
            delattr(self.ROOT, 'rnum0')
        if hasattr(self, 'autoRoot'):
            self.ROOT = self.autoRoot
            self.ROOT.rnum0 = 0
            return
        if len(self.chains) > 1:
            return "AutoRoot not implemented for molecules with >1 chain"
        self.bestBranch = len(self.allAtoms)
        bestList = []
        for item in self.allAtoms:
            if not hasattr(item, 'bonds'): continue
            if len(item.bonds) == 1 and item.bonds[0].leaf: continue
            if hasattr(item, 'leaf'): continue
            item.maxbranch = 0
            for b in item.bonds:
                nxtAtom = self._getnxtAtom(item, b)
                if not b.leaf:
                    thistree = self.subTree(item, nxtAtom, self.allAtoms)
                    thisbranch = len(thistree)
                    if thisbranch > item.maxbranch:
                        item.maxbranch = thisbranch
            #bestList holds all current best choices for Root..
            if item.maxbranch < self.bestBranch:
                bestList = []
                bestList.append(item)
                self.bestBranch = item.maxbranch
            if item.maxbranch == self.bestBranch and item not in bestList:
                bestList.append(item)
        if len(bestList) > 1:
            foundCycle = 0
            for at in bestList:
                at.cycleatom = 0
                for b in at.bonds:
                    #4/29: peptides won't have aromatic
                    #but will have incycle
                    #if b.bondOrder=='aromatic':
                    if hasattr(b, 'incycle'):
                        at.cycleatom = 1
                        continue
            for at in bestList:
                if at.cycleatom:
                    self.ROOT = at
                    self.autoRoot = at
                    self.ROOT.rnum0 = 0
                    foundCycle = 1
                    break
            #if bestList had a cycle atom, it's been set to root..if NOT:
            if not foundCycle:
                self.autoRoot = bestList[0]
                self.ROOT = bestList[0]
                self.ROOT.rnum0 = 0
        #no ties for possible root, use bestRoot...
        else:
            self.autoRoot = bestList[0]
            self.ROOT = bestList[0]
            self.ROOT.rnum0 = 0
        return self.ROOT

    def buildTorsionTree(self):
        if not hasattr(self, 'ROOT'):
            print('must set ROOT first!')
            return
        self.torTree = TorTree(self.parser, self.ROOT)

    def turnOnTorsion(self, bnd):
        """
        turnOnTorsion 
        """
        if not bnd in self.rotatable:
            return
        #this is redundant (??)
        if not bnd.possibleTors:
            return
        if not bnd.activeTors:
            self.torscount = self.torscount + 1
            bnd.activeTors = 1
            self.torTree.addTorsion(bnd.atom1.number, bnd.atom2.number)

    def turnOffTorsion(self, bnd):
        """
        turnOffTorsion 
        """
        if not bnd in self.rotatable:
            return
        #this is redundant (??)
        if not bnd.possibleTors:
            return
        if bnd.activeTors:
            self.torscount = self.torscount - 1
            bnd.activeTors = 0
            self.torTree.removeTorsion(bnd.atom1.number, bnd.atom2.number)

    def limitTorsions(self, numTors, type, simpleModel=1):
        """sets number of torsions to specified number by inactivating either those which
move the fewest atoms or those which move the most. if number is > than
current but less than possible, torsions are reactivated
        """

        print('lT: numTors=', numTors, ' type=', type)
        allAts = self.allAtoms
        root = self.ROOT
        torscount = self.torscount
        print('torscount=', torscount, end=' ')
        #NB: torscount is not necessarily the max
        #ie it could have been adjusted already
        at0 = self.allAtoms[0]
        if not hasattr(self, 'torTree') or not hasattr(at0, 'tt_ind'):
            self.torTree = TorTree(self.parser, root)
        torsionMap = self.torTree.torsionMap
        torsionMapNum = len(torsionMap)
        print('len(tm)=', torsionMapNum)
        possibleTors = self.possible_torscount
        if simpleModel:
            self.setTorsions(numTors, type)
            return

        #FIX THIS: what if want to increase active torsions
        if torscount == numTors:
            msg = 'specified number==number present: no adjustment'
            return 'msg'
        elif torscount < numTors:
            if torscount == possibleTors:
                #if torscount==torsionMapNum:
                msg = 'specified number == number possible: no adjustment'
                return msg
            else:
                #in this case turn on as many as possible
                #if numTors>=torsionMapNum:
                if numTors >= possibleTors:
                    #turn on everything
                    delta = possibleTors - torscount
                    #delta = torsionMapNum - torscount
                else:
                    delta = numTors - torscount
                self.turnOnTorsions(delta, type)
        else:
            #torscount>numTors
            #in this case turn them off
            delta = torscount - numTors
            self.turnOffTorsions(delta, type)
        msg = str(delta) + ' torsions changed'
        return msg

    def setTorsions(self, numTors, type):
        #this assumes atoms have tt_ind field
        tNum = len(self.torTree.base_torsionMap)
        msg = ""
        if numTors > tNum:
            msg = 'too many torsions specified! ' + str(
                numTors) + ' reducing to' + str(tNum)
            numTors = tNum
        if type == 'fewest':
            rangeList = list(range(numTors))
        else:
            rangeList = []
            for k in range(1, numTors + 1):
                rangeList.append(-k)
        #turn them all off
        baseTorsionMap = self.torTree.base_torsionMap
        torsionMap = self.torTree.torsionMap
        #turn all active nodes off
        active_nodes = torsionMap[:]
        for node in active_nodes:
            #print 'turning off node ', node.number
            self.torTree.removeTorsion(node.bond[0], node.bond[1])
            b = self.allAtoms.get(
                lambda x, node=node: x.tt_ind in node.bond).bonds[0][0]
            b.activeTors = 0

        #turn on the right number at correct end
        for i in rangeList:
            node = baseTorsionMap[i]
            #print '2:turning on node ', node.number
            self.torTree.addTorsion(node.bond[0], node.bond[1])
            b = self.allAtoms.get(
                lambda x, node=node: x.tt_ind in node.bond).bonds[0][0]
            b.activeTors = 1
            #print 'now len(torsionMap)=', len(torsionMap)
        self.torscount = numTors
        return msg

    def turnOnTorsions(self, delta, type='fewest'):
        allAts = self.allAtoms
        torsionMap = self.torTree.torsionMap
        baseTorsionMap = self.torTree.base_torsionMap
        torscount = self.torscount
        #turn on delta torsions + adjust torscount in dict
        if type == 'fewest':
            rangeList = list(range(delta))
        else:
            rangeList = []
            for k in range(1, delta + 1):
                rangeList.append(-k)
        #FIX THIS: probably doesn't work
        for i in rangeList:
            node = baseTorsionMap[i]
            b = allAts.get(
                lambda x, node=node: x.tt_ind in node.bond).bonds[0][0]
            if not b.activeTors:
                self.torTree.addTorsion(node.bond[0], node.bond[1])
                b.activeTors = 1
            else:
                lastInd = rangeList[-1]
                if type == 'fewest':
                    rangeList.append(lastInd + 1)
                else:
                    rangeList.append(lastInd - 1)
        #torscount should be torscount + delta here
        self.torscount = torscount + delta

    def turnOffTorsions(self, delta, type='fewest'):
        allAts = self.allAtoms
        torsionMap = self.torTree.torsionMap
        baseTorsionMap = self.torTree.base_torsionMap
        torscount = self.torscount
        #turn on delta torsions + adjust torscount in dict
        if type == 'fewest':
            rangeList = list(range(delta))
        else:
            rangeList = []
            for k in range(1, delta + 1):
                rangeList.append(-k)
        for i in rangeList:
            node = baseTorsionMap[i]
            if node.bond == (None, None):
                print('error in turnOff torsions with ', rangeList)
                break
            b = allAts.get(
                lambda x, node=node: x.tt_ind in node.bond).bonds[0][0]
            if b.activeTors:
                self.torTree.removeTorsion(node.bond[0], node.bond[1])
                b.activeTors = 0
            else:
                lastInd = rangeList[-1]
                if type == 'fewest':
                    rangeList.append(lastInd + 1)
                else:
                    rangeList.append(lastInd - 1)
        self.torscount = torscount - delta

    def setCarbonName(self, at):
        """
        setCarbonName 
        """
        if not at.element == 'C':
            return
        if len(at.name) == 1:
            at.name = 'C'
        else:
            at.name = 'C' + at.name[1:]

    def setAromaticName(self, at):
        """
        setAromaticName 
        """
        if not at.element == 'C':
            return
        if len(at.name) == 1:
            at.name = 'A'
        else:
            at.name = 'A' + at.name[1:]