def _findTarget(fromAtom, atPos, toAtom, asDonor, hbondInfo, finished): if toAtom in coordinations.get(fromAtom, []): return toAtom.xformCoord() if toAtom in finished: toInfo = hbondInfo[toAtom] # known positioning already protons, lonePairs = toInfo if asDonor: positions = lonePairs else: positions = protons nearest = None for pos in positions: dsq = (pos - atPos).sqlength() if not nearest or dsq < nsq: nearest = pos nsq = dsq return nearest toHPos = [] toBonded = [] toCoplanar = [] toGeom = _typeInfo(toAtom).geometry for tb in toAtom.primaryNeighbors(): toBonded.append(tb.xformCoord()) if tb.element.number == 1: toHPos.append(tb.xformCoord()) if toGeom == planar: toAtomLocs = toAtom.allLocations() for btb in tb.primaryNeighbors(): if btb in toAtomLocs: continue toCoplanar.append(btb.xformCoord()) if asDonor: # point towards nearest lone pair targets = bondPositions(toAtom.xformCoord(), toGeom, vdwRadius(toAtom), toBonded, coPlanar=toCoplanar, toward=atPos) else: # point to nearest hydrogen if _typeInfo(toAtom).substituents <= _numBonds(toAtom): targets = toHPos else: targets = toHPos + bondPositions( toAtom.xformCoord(), toGeom, bondWithHLength(toAtom, toGeom), toBonded, coPlanar=toCoplanar, toward=atPos) nearest = None for target in targets: dsq = (target - atPos).sqlength() if not nearest or dsq < nsq: nearest = target nsq = dsq return nearest
def addNewOs(new_res, psi, **kw): new_N = new_res.atomsMap['N'][0] new_CA = new_res.atomsMap['CA'][0] new_C = new_res.atomsMap['C'][0] new_OXT = addDihedralAtom('OXT', chimera.Element(8), new_C, new_CA, new_N, DIST_C_O, 114, psi, residue=new_res) new_OXT.drawMode = kw['atomDrawMode'] new_OXT.bfactor = kw['bFactor'] addBond(new_OXT, new_C, drawMode=kw['bondDrawMode']) avail_bond_pos = bondPositions(bondee=new_C.coord(), geom=chimera.Atom.Planar, bondLen=DIST_C_O, bonded=[a.coord() for a in new_C.neighbors]) use_point = avail_bond_pos[0] new_O = addAtom('O', chimera.Element(8), new_res, use_point) new_O.drawMode = kw['atomDrawMode'] new_O.bfactor = kw['bFactor'] addBond(new_O, new_C, drawMode=kw['bondDrawMode']) return new_O, new_OXT
def addNewCA(last_bbone_ats, new_res, **kw): last_CA = last_bbone_ats['CA'] last_C = last_bbone_ats['C'] last_O = last_bbone_ats['O'] new_N = new_res.atomsMap['N'][0] ## add the CA atom (not dihedral!) ## first, find the location for the new CA atom new_CA_bond_pos = bondPositions( bondee=new_N.coord(), geom=chimera.Atom.Planar, bondLen=DIST_CA_N, bonded=[a.coord() for a in new_N.neighbors], coPlanar=[last_CA.coord(), last_O.coord()]) shortest_point = getShortestPoint(last_O.coord(), new_CA_bond_pos) new_CA = addAtom('CA', chimera.Element(6), new_res, shortest_point) new_CA.drawMode = kw['atomDrawMode'] new_CA.bfactor = kw['bFactor'] addBond(new_N, new_CA, drawMode=kw['bondDrawMode']) return new_CA
def addHydrogens(atom, bondingInfo, namingSchema, totalHydrogens, idatmType, invert, coordinations): away = away2 = planar = None geom = bondingInfo.geometry substs = bondingInfo.substituents curBonds = len(atom.primaryBonds()) needed = substs - curBonds if needed <= 0: return atPos = atom.xformCoord() exclude = coordinations + atom.primaryNeighbors() if geom == 3 and curBonds == 1: bonded = atom.primaryNeighbors()[0] grandBonded = bonded.primaryNeighbors() grandBonded.remove(atom) if len(grandBonded) < 3: planar = [a.xformCoord() for a in grandBonded] if geom == 4 and not exclude: away, d, natom = findNearest(atPos, atom, exclude, 3.5) if away: away2, d2, natom2 = findRotamerNearest(atPos, idatmType[atom], atom, natom, 3.5) elif geom == 4 and len(exclude) == 1: away, d, natom = findRotamerNearest(atPos, idatmType[atom], atom, exclude[0], 3.5) else: away, d, natom = findNearest(atPos, atom, exclude, 3.5) bondedPos = [] for bonded in atom.primaryNeighbors(): bondedPos.append(bonded.xformCoord()) if coordinations: toward = coordinations[0].xformCoord() away2 = away away = None else: toward = None positions = bondPositions(atPos, geom, bondWithHLength(atom, geom), bondedPos, toward=toward, coPlanar=planar, away=away, away2=away2) if coordinations: coordPos = None for pos in positions: d = pos.sqdistance(toward) if coordPos is None or d < lowest: coordPos = pos lowest = d positions.remove(coordPos) if len(positions) > needed: positions = roomiest(positions, atom, 3.5)[:needed] for i, pos in enumerate(positions): newHydrogen(atom, i + 1, totalHydrogens, namingSchema, invert.apply(pos))
def xformCoord(self): primary = self.primaryNeighbors()[0] pos = bondPositions(primary.xformCoord(), 4, 1.0, bonds) class FakeCoord: pass crd = FakeCoord() crd = pos[0] return crd
def addHydrogens(atom, bondingInfo, namingSchema, totalHydrogens, idatmType, invert, coordinations): away = away2 = planar = None geom = bondingInfo.geometry substs = bondingInfo.substituents curBonds = len(atom.primaryBonds()) needed = substs - curBonds if needed <= 0: return atPos = atom.xformCoord() exclude = coordinations + atom.primaryNeighbors() if geom == 3 and curBonds == 1: bonded = atom.primaryNeighbors()[0] grandBonded = bonded.primaryNeighbors() grandBonded.remove(atom) if len(grandBonded) < 3: planar = [a.xformCoord() for a in grandBonded] if geom == 4 and not exclude: away, d, natom = findNearest(atPos, atom, exclude, 3.5) if away: away2, d2, natom2 = findRotamerNearest(atPos, idatmType[atom], atom, natom, 3.5) elif geom == 4 and len(exclude) == 1: away, d, natom = findRotamerNearest(atPos, idatmType[atom], atom, exclude[0], 3.5) else: away, d, natom = findNearest(atPos, atom, exclude, 3.5) bondedPos = [] for bonded in atom.primaryNeighbors(): bondedPos.append(bonded.xformCoord()) if coordinations: toward = coordinations[0].xformCoord() away2 = away away = None else: toward = None positions = bondPositions(atPos, geom, bondWithHLength(atom, geom), bondedPos, toward=toward, coPlanar=planar, away=away, away2=away2) if coordinations: coordPos = None for pos in positions: d = pos.sqdistance(toward) if coordPos is None or d < lowest: coordPos = pos lowest = d positions.remove(coordPos) if len(positions) > needed: positions = roomiest(positions, atom, 3.5)[:needed] for i, pos in enumerate(positions): newHydrogen(atom, i+1, totalHydrogens, namingSchema, invert.apply(pos))
def _methylate(na, n, atomNames): added = [] aname = _getAName("C", atomNames) from chimera.molEdit import addAtom nn = addAtom(aname, chimera.Element('C'), na.residue, n.coord()) added.append(nn) na.molecule.newBond(na, nn) from chimera.bondGeom import bondPositions for pos in bondPositions(nn.coord(), 4, 1.1, [na.coord()]): hname = _getAName("H", atomNames) nh = addAtom(hname, chimera.Element('H'), na.residue, pos) added.append(nh) na.molecule.newBond(nn, nh) return added
def _methylate(na, n, atomNames): added = [] aname = _getAName("C", atomNames) from chimera.molEdit import addAtom nn = addAtom(aname, chimera.Element('C'), na.residue, n.coord()) added.append(nn) na.molecule.newBond(na, nn) from chimera.bondGeom import bondPositions for pos in bondPositions(nn.coord(), 4, 1.1, [na.coord()]): hname = _getAName("H", atomNames) nh = addAtom(hname, chimera.Element('H'), na.residue, pos) added.append(nh) na.molecule.newBond(nn, nh) return added
def _tet2check(tetPos, toward, toward2, partnerPos): if debug: print "2 position tetCheck", for pos in bondPositions(tetPos, 4, 1.0, [], toward=toward, toward2=toward2): if debug: print chimera.angle(partnerPos, tetPos, pos), if chimera.angle(partnerPos, tetPos, pos) < _testAngles[4]: if debug: print "true" return True if debug: print "false" return False
def completeTerminalCarboxylate(cter): from chimera.bondGeom import bondPositions from chimera.molEdit import addAtom if "OXT" in cter.atomsMap: return try: cs = cter.atomsMap["C"] except KeyError: return for c in cs: # alt locs are possible if len(c.primaryBonds()) != 2: return loc = bondPositions(c.coord(), 3, 1.229, [n.coord() for n in c.primaryNeighbors()])[0] oxt = addAtom("OXT", chimera.Element("O"), cter, loc, bondedTo=c) replyobj.info("Missing OXT added to C-terminal residue %s\n" % str(cter))
def addNewN(last_bbone_ats, new_res, **kw): ## add the N atom of new residue's backbone to the last residue last_C = last_bbone_ats['C'] new_N_bond_pos = bondPositions( bondee=last_C.coord(), geom=chimera.Atom.Planar, bondLen=DIST_N_C, bonded=[a.coord() for a in last_C.neighbors]) use_point = new_N_bond_pos[0] new_N = addAtom('N', chimera.Element(7), new_res, use_point) new_N.drawMode = kw['atomDrawMode'] new_N.bfactor = kw['bFactor'] addBond(last_C, new_N, drawMode=kw['bondDrawMode']) return new_N
def reposLastO(last_res, last_bbone_ats, atom_drawMode, bond_drawMode): ## reposition the last residue's 'O' atom last_O = getBboneAtom(last_res, 'O') last_res.molecule.deleteAtom(last_O) last_C = last_bbone_ats['C'] last_CA = last_bbone_ats['CA'] old_O_bond_pos = bondPositions( bondee=last_C.coord(), geom=chimera.Atom.Planar, bondLen=1.229, bonded=[a.coord() for a in last_C.neighbors]) ## should only be one spot left old_O_bond_pos = old_O_bond_pos[0] new_O = addAtom('O', chimera.Element(8), last_res, old_O_bond_pos) new_O.drawMode = atom_drawMode addBond(new_O, last_C, drawMode=bond_drawMode) return new_O
def accThetaTau(donor, donorHyds, acceptor, upsilonPartner, r2, upsilonLow, upsilonHigh, theta): if base.verbose: print "accThetaTau" dp = donor.xformCoord() ap = acceptor.xformCoord() if donor.element.name == "S": r2 = sulphurCompensate(r2) if base.verbose: print "distance: %g, cut off: %g" % (distance(dp, ap), sqrt(r2)) if sqdistance(dp, ap) > r2: if base.verbose: print "dist criteria failed" return 0 if base.verbose: print "dist criteria okay" if upsilonPartner: upPos = upsilonPartner.xformCoord() else: # upsilon measured from "lone pair" (bisector of attached # atoms) bondedPos = [] for bonded in acceptor.primaryNeighbors(): bondedPos.append(bonded.xformCoord()) lonePairs = bondPositions(ap, tetrahedral, 1.0, bondedPos) bisectors = [] for lp in lonePairs: bisectors.append(ap - (lp - ap)) upPos = bisectors[0] for bs in bisectors[1:]: if base.verbose: print "Testing 'extra' lone pair" if testThetaTau(dp, donorHyds, ap, bs, upsilonLow, upsilonHigh, theta): return 1 return testThetaTau(dp, donorHyds, ap, upPos, upsilonLow, upsilonHigh, theta)
def swap(res, newRes, preserve=0, bfactor=True): """change 'res' into type 'newRes'""" if res == "HIS": res = "HIP" fixed, buds, start, end = getResInfo(res) tmplRes = chimera.restmplFindResidue(newRes, start, end) if not tmplRes: raise ValueError, "No connectivity template for residue '%s'"\ % newRes # sanity check: does the template have the bud atoms? for bud in buds: if not tmplRes.atomsMap.has_key(bud): raise ValueError, "New residue type (%s) " \ "not compatible with starting residue type (%s)" \ % (newRes, res.type) # if bfactor not specified, find highest bfactor in molecule # and use that for swapped-in atoms if bfactor is False: bfactor = None if bfactor is True: for a in res.molecule.atoms: try: if bfactor is True or a.bfactor > bfactor: bfactor = a.bfactor except AttributeError: pass if preserve: raise ValueError, "'preserve' keyword not yet implemented" # prune non-backbone atoms for a in res.oslChildren(): if a.name not in fixed: a.molecule.deleteAtom(a) # add new sidechain while len(buds) > 0: bud = buds.pop() tmplBud = tmplRes.atomsMap[bud] resBud = res.atomsMap[bud][0] try: info = typeInfo[tmplBud.idatmType] geom = info.geometry subs = info.substituents except KeyError: print tmplBud.idatmType raise AssertionError, "Can't determine atom type" \ " information for atom %s of residue %s" % ( bud, res.oslIdent()) # use coord() rather than xformCoord(): we want to set # the new atom's coord(), to which the proper xform will # then be applied for a, b in tmplBud.bondsMap.items(): if a.element.number == 1: # don't add hydrogens continue if res.atomsMap.has_key(a.name): resBonder = res.atomsMap[a.name][0] if not resBud.bondsMap.has_key(resBonder): addBond(a, resBonder) continue newAtom = None numBonded = len(resBud.bonds) if numBonded >= subs: raise AssertionError, \ "Too many atoms bonded to %s of" \ " residue %s" % (bud, res.oslIdent()) if numBonded == 0: raise AssertionError, \ "Atom %s of residue %s has no" \ " neighbors after pruning?!?" % ( bud, res.oslIdent()) elif numBonded == 1: # only one connected atom -- have to use # dihedral real1 = resBud.neighbors[0] newAtom = formDihedral(resBud, real1, tmplRes, a, b) elif numBonded == 2: crd = resBud.coord() bonded = resBud.neighbors bcrds = map(lambda a: a.coord(), bonded) positions = bondPositions(crd, geom, b.length(), bcrds) if len(positions) > 1: # need to disambiguate choices; # check improper dihedral # but first, make sure the bonded # atoms are in this residue inres = filter(lambda a, r=res: a.residue == r, bonded) if len(inres) == 0: raise AssertionError, \ "Can't disambiguate " \ "tetrahedral position by " \ "forming in-residue dihedral " \ "for %s of residue %s" % ( bud, res.oslIdent()) if len(inres) == 1: newAtom = formDihedral(resBud, inres[0], tmplRes, a, b) else: # okay, check improper dihedral tmplBonded = map(lambda a, tra=tmplRes.atomsMap: tra[a.name], bonded) tmplPts = map(lambda a: a.coord(), [a, tmplBud] + tmplBonded) tmplDihed = apply(dihedral, tmplPts, {}) backPts = map(lambda a: a.coord(), [resBud] + bonded) dh1 = apply(dihedral, tuple( positions[:1] + backPts), {}) dh2 = apply(dihedral, tuple( positions[1:] + backPts), {}) diff1 = abs(dh1 - tmplDihed) diff2 = abs(dh2 - tmplDihed) if diff1 > 180: diff1 = 360 - diff1 if diff2 > 180: diff2 = 360 - diff2 if diff1 < diff2: position = positions[0] else: position = positions[1] else: position = positions[0] else: crd = resBud.coord() bonded = resBud.neighbors bcrds = map(lambda a: a.coord(), bonded) positions = bondPositions(crd, geom, b.length(), bcrds) if len(positions) > 1: raise AssertionError, \ "Too many positions returned" \ " for atom %s of residue %s" % ( bud, res.oslIdent()) position = positions[0] if not newAtom: newAtom = addAtom(a.name, a.element, res, position) newAtom.drawMode = resBud.drawMode if bfactor is not None and bfactor is not True: newAtom.bfactor = bfactor # TODO: need to iterate over coordSets for bonded in a.bondsMap.keys(): if not res.atomsMap.has_key(bonded.name): continue addBond(newAtom, res.atomsMap[bonded.name][0]) buds.append(newAtom.name) res.label = res.label.replace(res.type, newRes) res.type = newRes
def swap(res, newRes, preserve=0, bfactor=True): """change 'res' into type 'newRes'""" if res == "HIS": res = "HIP" fixed, buds, start, end = getResInfo(res) tmplRes = chimera.restmplFindResidue(newRes, start, end) if not tmplRes: raise ValueError, "No connectivity template for residue '%s'"\ % newRes # sanity check: does the template have the bud atoms? for bud in buds: if not tmplRes.atomsMap.has_key(bud): raise ValueError, "New residue type (%s) " \ "not compatible with starting residue type (%s)" \ % (newRes, res.type) # if bfactor not specified, find highest bfactor in molecule # and use that for swapped-in atoms if bfactor is False: bfactor = None if bfactor is True: for a in res.molecule.atoms: try: if bfactor is True or a.bfactor > bfactor: bfactor = a.bfactor except AttributeError: pass if preserve: raise ValueError, "'preserve' keyword not yet implemented" # prune non-backbone atoms for a in res.oslChildren(): if a.name not in fixed: a.molecule.deleteAtom(a) # add new sidechain while len(buds) > 0: bud = buds.pop() tmplBud = tmplRes.atomsMap[bud] resBud = res.atomsMap[bud][0] try: info = typeInfo[tmplBud.idatmType] geom = info.geometry subs = info.substituents except KeyError: print tmplBud.idatmType raise AssertionError, "Can't determine atom type" \ " information for atom %s of residue %s" % ( bud, res.oslIdent()) # use coord() rather than xformCoord(): we want to set # the new atom's coord(), to which the proper xform will # then be applied for a, b in tmplBud.bondsMap.items(): if a.element.number == 1: # don't add hydrogens continue if res.atomsMap.has_key(a.name): resBonder = res.atomsMap[a.name][0] if not resBud.bondsMap.has_key(resBonder): addBond(a, resBonder) continue newAtom = None numBonded = len(resBud.bonds) if numBonded >= subs: raise AssertionError, \ "Too many atoms bonded to %s of" \ " residue %s" % (bud, res.oslIdent()) if numBonded == 0: raise AssertionError, \ "Atom %s of residue %s has no" \ " neighbors after pruning?!?" % ( bud, res.oslIdent()) elif numBonded == 1: # only one connected atom -- have to use # dihedral real1 = resBud.neighbors[0] newAtom = formDihedral(resBud, real1, tmplRes, a, b) elif numBonded == 2: crd = resBud.coord() bonded = resBud.neighbors bcrds = map(lambda a: a.coord(), bonded) positions = bondPositions(crd, geom, b.length(), bcrds) if len(positions) > 1: # need to disambiguate choices; # check improper dihedral # but first, make sure the bonded # atoms are in this residue inres = filter(lambda a, r=res: a.residue == r, bonded) if len(inres) == 0: raise AssertionError, \ "Can't disambiguate " \ "tetrahedral position by " \ "forming in-residue dihedral " \ "for %s of residue %s" % ( bud, res.oslIdent()) if len(inres) == 1: newAtom = formDihedral(resBud, inres[0], tmplRes, a, b) else: # okay, check improper dihedral tmplBonded = map( lambda a, tra=tmplRes.atomsMap: tra[a.name], bonded) tmplPts = map(lambda a: a.coord(), [a, tmplBud] + tmplBonded) tmplDihed = apply(dihedral, tmplPts, {}) backPts = map(lambda a: a.coord(), [resBud] + bonded) dh1 = apply(dihedral, tuple(positions[:1] + backPts), {}) dh2 = apply(dihedral, tuple(positions[1:] + backPts), {}) diff1 = abs(dh1 - tmplDihed) diff2 = abs(dh2 - tmplDihed) if diff1 > 180: diff1 = 360 - diff1 if diff2 > 180: diff2 = 360 - diff2 if diff1 < diff2: position = positions[0] else: position = positions[1] else: position = positions[0] else: crd = resBud.coord() bonded = resBud.neighbors bcrds = map(lambda a: a.coord(), bonded) positions = bondPositions(crd, geom, b.length(), bcrds) if len(positions) > 1: raise AssertionError, \ "Too many positions returned" \ " for atom %s of residue %s" % ( bud, res.oslIdent()) position = positions[0] if not newAtom: newAtom = addAtom(a.name, a.element, res, position) newAtom.drawMode = resBud.drawMode if bfactor is not None and bfactor is not True: newAtom.bfactor = bfactor # TODO: need to iterate over coordSets for bonded in a.bondsMap.keys(): if not res.atomsMap.has_key(bonded.name): continue addBond(newAtom, res.atomsMap[bonded.name][0]) buds.append(newAtom.name) res.label = res.label.replace(res.type, newRes) res.type = newRes
def changeAtom(atom, element, geometry, numBonds, autoClose=True, name=None): if len(atom.primaryBonds()) > numBonds: raise ParamError("Atom already has more bonds than requested.\n" "Either delete some bonds or choose a different number" " of requested bonds.") from chimera.molEdit import addAtom, genAtomName changedAtoms = [atom] if not name: name = genAtomName(element, atom.residue) changeAtomName(atom, name) atom.element = element if hasattr(atom, 'mol2type'): delattr(atom, 'mol2type') # if we only have one bond, correct its length if len(atom.primaryBonds()) == 1: neighbor = atom.primaryNeighbors()[0] newLength = bondLength(atom, geometry, neighbor.element, a2info=(neighbor, numBonds)) setBondLength(atom.primaryBonds()[0], newLength, movingSide="smaller side") if numBonds == len(atom.primaryBonds()): return changedAtoms from chimera.bondGeom import bondPositions coPlanar = None if geometry == 3 and len(atom.primaryBonds()) == 1: n = atom.primaryNeighbors()[0] if len(n.primaryBonds()) == 3: coPlanar = [nn.coord() for nn in n.primaryNeighbors() if nn != atom] away = None if geometry == 4 and len(atom.primaryBonds()) == 1: n = atom.primaryNeighbors()[0] if len(n.primaryBonds()) > 1: nn = n.primaryNeighbors()[0] if nn == atom: nn = n.primaryNeighbors()[1] away = nn.coord() hydrogen = Element("H") positions = bondPositions(atom.coord(), geometry, bondLength(atom, geometry, hydrogen), [n.coord() for n in atom.primaryNeighbors()], coPlanar=coPlanar, away=away)[:numBonds-len(atom.primaryBonds())] if autoClose: if len(atom.molecule.atoms) < 100: testAtoms = atom.molecule.atoms else: from CGLutil.AdaptiveTree import AdaptiveTree tree = AdaptiveTree([a.coord().data() for a in atom.molecule.atoms], a.molecule.atoms, 2.5) testAtoms = tree.searchTree(atom.coord().data(), 5.0) else: testAtoms = [] for pos in positions: for ta in testAtoms: if ta == atom: continue testLen = bondLength(ta, 1, hydrogen) testLen2 = testLen * testLen if (ta.coord() - pos).sqlength() < testLen2: bonder = ta # possibly knock off a hydrogen to # accomodate the bond... for bn in bonder.primaryNeighbors(): if bn.element.number > 1: continue if chimera.angle(atom.coord() - ta.coord(), bn.coord() - ta.coord()) > 45.0: continue if bn in testAtoms: testAtoms.remove(bn) atom.molecule.deleteAtom(bn) break break else: bonder = addAtom(genAtomName(hydrogen, atom.residue), hydrogen, atom.residue, pos, bondedTo=atom) changedAtoms.append(bonder) return changedAtoms
def changeAtom(atom, element, geometry, numBonds, autoClose=True, name=None): if len(atom.primaryBonds()) > numBonds: raise ParamError( "Atom already has more bonds than requested.\n" "Either delete some bonds or choose a different number" " of requested bonds.") from chimera.molEdit import addAtom, genAtomName changedAtoms = [atom] if not name: name = genAtomName(element, atom.residue) changeAtomName(atom, name) atom.element = element if hasattr(atom, 'mol2type'): delattr(atom, 'mol2type') # if we only have one bond, correct its length if len(atom.primaryBonds()) == 1: neighbor = atom.primaryNeighbors()[0] newLength = bondLength(atom, geometry, neighbor.element, a2info=(neighbor, numBonds)) setBondLength(atom.primaryBonds()[0], newLength, movingSide="smaller side") if numBonds == len(atom.primaryBonds()): return changedAtoms from chimera.bondGeom import bondPositions coPlanar = None if geometry == 3 and len(atom.primaryBonds()) == 1: n = atom.primaryNeighbors()[0] if len(n.primaryBonds()) == 3: coPlanar = [ nn.coord() for nn in n.primaryNeighbors() if nn != atom ] away = None if geometry == 4 and len(atom.primaryBonds()) == 1: n = atom.primaryNeighbors()[0] if len(n.primaryBonds()) > 1: nn = n.primaryNeighbors()[0] if nn == atom: nn = n.primaryNeighbors()[1] away = nn.coord() hydrogen = Element("H") positions = bondPositions(atom.coord(), geometry, bondLength(atom, geometry, hydrogen), [n.coord() for n in atom.primaryNeighbors()], coPlanar=coPlanar, away=away)[:numBonds - len(atom.primaryBonds())] if autoClose: if len(atom.molecule.atoms) < 100: testAtoms = atom.molecule.atoms else: from CGLutil.AdaptiveTree import AdaptiveTree tree = AdaptiveTree( [a.coord().data() for a in atom.molecule.atoms], a.molecule.atoms, 2.5) testAtoms = tree.searchTree(atom.coord().data(), 5.0) else: testAtoms = [] for pos in positions: for ta in testAtoms: if ta == atom: continue testLen = bondLength(ta, 1, hydrogen) testLen2 = testLen * testLen if (ta.coord() - pos).sqlength() < testLen2: bonder = ta # possibly knock off a hydrogen to # accomodate the bond... for bn in bonder.primaryNeighbors(): if bn.element.number > 1: continue if chimera.angle(atom.coord() - ta.coord(), bn.coord() - ta.coord()) > 45.0: continue if bn in testAtoms: testAtoms.remove(bn) atom.molecule.deleteAtom(bn) break break else: bonder = addAtom(genAtomName(hydrogen, atom.residue), hydrogen, atom.residue, pos, bondedTo=atom) changedAtoms.append(bonder) return changedAtoms
def hydPositions(heavy, includeLonePairs=False): """Return list of positions for hydrogens attached to this atom. If a hydrogen could be in one of several positions, don't return any of those. """ # first, find known attached atoms bondedHeavys = [] hyds = [] for atom in heavy.primaryNeighbors(): if atom.element.number > 1: bondedHeavys.append(atom) else: hyds.append(atom) # convert to Points hydLocs = [] for hyd in hyds: hydLocs.append(hyd.xformCoord()) if hydLocs and not includeLonePairs: # explicit hydrogens "win" over atom types return hydLocs if typeInfo.has_key(heavy.idatmType): info = typeInfo[heavy.idatmType] geom = info.geometry if includeLonePairs: subs = geom else: subs = info.substituents bondedLocs = hydLocs[:] for bHeavy in bondedHeavys: bondedLocs.append(bHeavy.xformCoord()) else: return hydLocs knownSubs = len(bondedLocs) if knownSubs >= subs or knownSubs == 0: return hydLocs # above eliminates 'single' geometry if knownSubs == 1 and geom == tetrahedral: # rotamer return hydLocs maxSubs = geom if maxSubs - subs > 0: # the "empty" bond could be anywhere return hydLocs heavyLoc = heavy.xformCoord() bondLen = Element.bondLength(heavy.element, Element(1)) if geom == planar: coPlanar = [] for bHeavy in bondedHeavys: try: bhGeom = typeInfo[bHeavy.idatmType].geometry except KeyError: bhGeom = None if bhGeom != planar: continue for atom in bHeavy.primaryNeighbors(): if atom != heavy: coPlanar.append(atom.xformCoord()) else: coPlanar = None hydLocs = hydLocs + bondPositions(heavyLoc, geom, bondLen, bondedLocs, coPlanar=coPlanar) return hydLocs
def _tryFinish(atom, hbondInfo, finished, aroAmines, prunedBy, processed): # do we have enough info to establish all H/LP positions for atom? bondingInfo = _typeInfo(atom) geom = bondingInfo.geometry # from number of donors/acceptors, determine # if we can position Hs/lone pairs numBonds = _numBonds(atom) hydsToPosition = bondingInfo.substituents - numBonds openings = geom - numBonds donors = [] acceptors = [] all = [] for isAcc, other in hbondInfo[atom]: all.append(other) if isAcc: donors.append(other) else: acceptors.append(other) if len(all) < openings \ and len(donors) < openings - hydsToPosition \ and len(acceptors) < hydsToPosition: if debug: print "not enough info (all/donors/acceptors):", len(all), len(donors), len(acceptors) return False # if so, find their positions and # record in hbondInfo; mark as finished atPos = atom.xformCoord() targets = [] for isAcc, other in hbondInfo[atom][:2]: targets.append(_findTarget(atom, atPos, other, not isAcc, hbondInfo, finished)) # for purposes of this intermediate measurement, use hydrogen # distances instead of lone pair distances; determine true # lone pair positions once hydrogens are found bondedPos = [] testPositions = [] coplanar = [] for bonded in atom.primaryNeighbors(): bondedPos.append(bonded.xformCoord()) if bonded.element.number == 1: testPositions.append(bonded.xformCoord()) if geom == planar: for btb in bonded.primaryNeighbors(): if btb == atom: continue coplanar.append(btb.xformCoord()) toward = targets[0] if len(targets) > 1: toward2 = targets[1] else: toward2 = None Hlen = bondWithHLength(atom, geom) LPlen = vdwRadius(atom) if debug: print atom.oslIdent(), "co-planar:", coplanar print atom.oslIdent(), "toward:", toward print atom.oslIdent(), "toward2:", toward2 normals = bondPositions(atPos, geom, 1.0, bondedPos, coPlanar=coplanar, toward=toward, toward2=toward2) if debug: print atom.oslIdent(), "bondPositions:", [str(x) for x in normals] # use vectors so we can switch between lone-pair length and H-length for normal in normals: testPositions.append(normal - atPos) # try to hook up positions with acceptors/donors if atom in aroAmines: if debug: print "delay finishing aromatic amine" return False all = {} protons = {} lonePairs = {} conflicting = [] for isAcc, other in hbondInfo[atom]: if debug: print "other:", other.oslIdent() nearest = None if other in finished: oprotons, olps = hbondInfo[other] if isAcc: opositions = oprotons mul = LPlen else: opositions = olps mul = Hlen for opos in opositions: for check in testPositions: if isinstance(check, chimera.Vector): pos = atPos + check * mul else: pos = check dsq = (opos - pos).sqlength() if nearest is None or dsq < nsq: nearest = check nsq = dsq else: otherPos = other.xformCoord() if isAcc: mul = LPlen else: mul = Hlen for check in testPositions: if isinstance(check, chimera.Vector): pos = atPos + check * mul else: pos = check dsq = (pos - otherPos).sqlength() if debug: print "dist from other to", if isinstance(check, chimera.Point): print "pre-existing proton:", elif check in all: if check in protons: print "new proton:", else: print "new lone pair:", else: print "unfilled position:", import math print math.sqrt(dsq) if nearest is None or dsq < nsq: nearest = check nsq = dsq if isinstance(nearest, chimera.Point): # closest to known hydrogen; no help in positioning... if isAcc: # other is trying to donate and is nearest # to one of our hydrogens conflicting.append((isAcc, other)) continue if nearest in all: if isAcc: if nearest in protons: conflicting.append((isAcc, other)) elif nearest in lonePairs: conflicting.append((isAcc, other)) continue # check for steric conflict (frequent with metal coordination) if isAcc: pos = atPos + nearest * LPlen atBump = 0.0 else: pos = atPos + nearest * Hlen atBump = Hrad checkDist = 2.19 + atBump # since searchTree is a module variable that changes, # need to access via the module... nearby = AddH.searchTree.searchTree(pos.data(), checkDist) stericClash = False okay = set([atom, other]) okay.update(atom.primaryNeighbors()) for nb in nearby: if nb in okay: continue if nb.molecule != atom.molecule \ and nb.molecule.id == atom.molecule.id: # ignore clashes with sibling submodels continue dChk = vdwRadius(nb) + atBump - 0.4 if dChk*dChk >= sqdistance(nb.xformCoord(), pos): stericClash = True if debug: print "steric clash with", nb.oslIdent(), "(%.3f < %.3f)" % (pos.distance(nb.xformCoord()), dChk) break if stericClash: conflicting.append((isAcc, other)) continue all[nearest] = 1 if isAcc: if debug: print "determined lone pair" lonePairs[nearest] = 1 else: if debug: print "determined proton" protons[nearest] = 1 for isAcc, other in conflicting: if debug: print "Removing hbond to %s due to conflict" % other.oslIdent() hbondInfo[atom].remove((isAcc, other)) if not hbondInfo[atom]: del hbondInfo[atom] if other in finished: continue try: hbondInfo[other].remove((not isAcc, atom)) if not hbondInfo[other]: del hbondInfo[other] except ValueError: pass # since any conflicting hbonds may have been used to determine # positions, determine the positions again with the remaining # hbonds if conflicting: # restore hbonds pruned by the conflicting hbonds for isAcc, other in conflicting: if isAcc: key = (other, atom) else: key = (atom, other) for phb in prunedBy.get(key, []): if debug: print "restoring %s/%s hbond pruned by hbond to %s" % (phb[0].oslIdent(), phb[1].oslIdent(), other.oslIdent()) processed.remove(phb) if atom not in hbondInfo: if debug: print "No non-conflicting hbonds left!" return False if debug: print "calling _tryFinish with non-conflicting hbonds" return _tryFinish(atom, hbondInfo, finished, aroAmines, prunedBy, processed) # did we determine enough positions? if len(all) < openings \ and len(protons) < hydsToPosition \ and len(lonePairs) < openings - hydsToPosition: if debug: print "not enough hookups (all/protons/lps):", len(all), len(protons), len(lonePairs) return False if len(protons) < hydsToPosition: for pos in testPositions: if isinstance(pos, chimera.Point): continue if pos not in all: protons[pos] = 1 Hlocs = [] for Hvec in protons.keys(): Hlocs.append(atPos + Hvec * Hlen) LPlocs = [] for vec in testPositions: if isinstance(vec, chimera.Point): continue if vec not in protons: LPlocs.append(atPos + vec * LPlen) hbondInfo[atom] = (Hlocs, LPlocs) finished[atom] = True return True
def addHydrogens(atomList, *args): """Add hydrogens to maximize h-bonding and minimize steric clashes. First, add hydrogens that are fixed and always present. Then, find H-bonds only to possibly-hydrogen-needing donors. For aromatic nitogens protonate at least one on a six-membered ring. For remaining rotamers with H-bonds, place hydrogens to maximize H-bond interactions. Then, for remaining rotamers, minimize steric clashes via torsion-driving. May not work well for atoms that are already partially hydrogenated. """ if debug: print "here (test)" if atomList: if len(atomList[0].molecule.coordSets) > 1: replyobj("Adding H-bond-preserving hydrogens to " "trajectories not supported.\n") return if debug: print "here 2" global addAtoms, typeInfo4Atom, namingSchemas, hydrogenTotals, \ idatmType, inversionCache, coordinations typeInfo4Atom, namingSchemas, hydrogenTotals, idatmType, hisNs, \ coordinations = args inversionCache = {} addAtoms = {} for atom in atomList: addAtoms[atom] = 1 from chimera import replyobj replyobj.status("Categorizing heavy atoms\n", blankAfter=0) problemAtoms = [] fixed = [] flat = [] aroNrings = {} global aroAmines aroAmines = {} unsaturated = [] saturated = [] finished = {} hbondInfo = {} for a, crds in coordinations.items(): hbondInfo[a] = [(True, crd) for crd in crds] for atom in atomList: bondingInfo = typeInfo4Atom[atom] numBonds = _numBonds(atom) substs = bondingInfo.substituents if numBonds >= substs: continue geom = bondingInfo.geometry if numBonds == 0: unsaturated.append(atom) elif numBonds == 1: if geom == linear: fixed.append(atom) elif geom == planar: if substs == 2: unsaturated.append(atom) else: if idatmType[atom] == 'Npl' \ and idatmType[ atom.primaryNeighbors()[0]] == 'Car': # primary aromatic amine aroAmines[atom] = True else: flat.append(atom) elif geom == tetrahedral: if substs < 4: unsaturated.append(atom) else: saturated.append(atom) else: raise AssertionError, "bad geometry for atom" \ " (%s) with one bond partner" \ % atom.oslIdent() elif numBonds == 2: if geom == tetrahedral: if substs == 3: unsaturated.append(atom) else: fixed.append(atom) elif geom == planar: aroNring = None if atom.element.number == 7: for ring in atom.minimumRings(): if ring.aromatic(): aroNring = ring break if aroNring: if aroNrings.has_key(aroNring): aroNrings[aroNring].append(atom) else: aroNrings[aroNring] = [atom] else: fixed.append(atom) else: raise AssertionError, "bad geometry for atom" \ " (%s) with two bond partners" \ % atom.oslIdent() elif numBonds == 3: if geom != tetrahedral: raise AssertionError, "bad geometry for atom" \ " (%s) with three bond partners" \ % atom.oslIdent() fixed.append(atom) # protonate aromatic nitrogens if only one nitrogen in ring: multiNrings = {} aroNs = {} for ring, ns in aroNrings.items(): if len(ns) == 1: fixed.append(ns[0]) elif ns[0] in hisNs: for n in ns: if hisNs[n]: fixed.append(n) else: multiNrings[ring] = ns for n in ns: aroNs[n] = True if debug: print len(fixed), "fixed" print len(flat), "flat" print len(aroAmines), "primary aromatic amines" print len(multiNrings), "aromatic multi-nitrogen rings" print len(unsaturated), "unsaturated" print len(saturated), "saturated rotamers" replyobj.status("Building search tree of atom positions\n", blankAfter=0) # since adaptive search tree is static, it will not include # hydrogens added after this; they will have to be found by # looking off their heavy atoms for needCoplanar in [False, True]: if needCoplanar: atoms = flat replyobj.status("Adding co-planar hydrogens\n", blankAfter=0) else: atoms = fixed replyobj.status("Adding simple fixed hydrogens\n", blankAfter=0) for atom in atoms: bondingInfo = _typeInfo(atom) geom = bondingInfo.geometry bondLength = bondWithHLength(atom, geom) neighbors = [] neighborAtoms = [] for n in atom.primaryNeighbors(): neighborAtoms.append(n) neighbors.append(n.xformCoord()) coplanar = [] if needCoplanar: for na in neighborAtoms: for nna in na.primaryNeighbors(): if nna != atom: coplanar.append( nna.xformCoord()) if len(coplanar) > 2: problemAtoms.append(atom) continue Hpositions = bondPositions(atom.xformCoord(), geom, bondLength, neighbors, coPlanar=coplanar) _attachHydrogens(atom, Hpositions) finished[atom] = True hbondInfo[atom] = (Hpositions, []) from FindHBond import findHBonds, recDistSlop, recAngleSlop from chimera import replyobj replyobj.status("Finding hydrogen bonds\n", blankAfter=0) donors = {} acceptors = {} for atom in unsaturated: donors[atom] = unsaturated acceptors[atom] = unsaturated for atom in saturated: donors[atom] = saturated for ring, ns in multiNrings.items(): for n in ns: donors[n] = ring acceptors[n] = ring for atom in aroAmines: donors[atom] = aroAmines acceptors[atom] = aroAmines hbonds = findHBonds(chimera.openModels.list( modelTypes=[chimera.Molecule]), distSlop=recDistSlop, angleSlop=recAngleSlop) replyobj.info("%d hydrogen bonds\n" % len(hbonds)) # want to assign hydrogens to strongest (shortest) hydrogen # bonds first, so sort by distance replyobj.status("Sorting hydrogen bonds by distance\n", blankAfter=0) sortableHbonds = [] for d, a in hbonds: if d in donors: sortableHbonds.append((d.xformCoord().sqdistance( a.xformCoord()), False, (d, a))) if a in acceptors: sortableHbonds.append((d.xformCoord().sqdistance( a.xformCoord()), True, (d, a))) sortableHbonds.sort() replyobj.status("Organizing h-bond info\n", blankAfter=0) hbonds = {} ambiguous = {} relBond = {} for dsq, isAcc, hbond in sortableHbonds: hbonds[hbond] = isAcc for da in hbond: relBond.setdefault(da, []).append(hbond) processed = set() breakAmbiguous = False breakNring = False candidates = {} candidates.update(donors) candidates.update(acceptors) pruned = set() prunedBy = {} reexamine = {} replyobj.status("Adding hydrogens by h-bond strength\n", blankAfter=0) while len(processed) < len(hbonds): replyobj.status("Adding hydrogens by h-bond strength (%d/%d)\n" % (len(processed), len(hbonds)), blankAfter=0) processedOne = False seenAmbiguous = {} for dsq, isAcc, hbond in sortableHbonds: if hbond in processed: continue d, a = hbond if hbond in pruned: if hbond in reexamine: del reexamine[hbond] if debug: print "re-examining", hbond[0].oslIdent(), "->", hbond[1].oslIdent() elif not breakAmbiguous: seenAmbiguous[d] = True seenAmbiguous[a] = True continue if (d not in candidates or d in finished) \ and (a not in candidates or a in finished): # not relevant processed.add(hbond) continue if (a, d) in hbonds \ and a not in finished \ and d not in finished \ and not _resolveAnilene(d, a, aroAmines, hbondInfo) \ and _angleCheck(d, a, hbondInfo) == (True, True): # possibly still ambiguous # if one or both ends are aromatic nitrogen, # then ambiguity depends on whether other # nitrogens in ring are finished and are # acceptors... nring = False try: ns = multiNrings[acceptors[a]] except (KeyError, TypeError): ns = [] if len(ns) > 1: nring = True for n in ns: if n not in finished: break protons, lps = hbondInfo[n] if protons: break else: # unambiguous, but in the # reverse direction! processed.add(hbond) if debug: print "reverse ambiguity resolved" continue unambiguous = False try: ns = multiNrings[donors[d]] except (KeyError, TypeError): ns = [] if len(ns) > 1: nring = True for n in ns: if n not in finished: break protons, lps = hbondInfo[n] if protons: break else: if debug: print "ambiguity resolved due to ring acceptors" unambiguous = True if not unambiguous: if not breakAmbiguous \ or nring and not breakNring: if debug: print "postponing", [a.oslIdent() for a in hbond] seenAmbiguous[d] = True seenAmbiguous[a] = True if hbond not in pruned: _doPrune(hbond, pruned, relBond, processed, prunedBy) continue if nring: if debug: print "breaking ambiguous N-ring hbond" else: if debug: print "breaking ambiguous hbond" if (a, d) in hbonds: if a in finished: if debug: print "breaking ambiguity because acceptor finished" elif d in finished: if debug: print "breaking ambiguity because donor finished" elif _resolveAnilene(d, a, aroAmines, hbondInfo): if debug: print "breaking ambiguity by resolving anilene" elif _angleCheck(d, a, hbondInfo) != (True, True): if debug: print "breaking ambiguity due to angle check" processed.add(hbond) from math import sqrt if debug: print "processed", d.oslIdent(), "->", a.oslIdent(), sqrt(dsq) # if donor or acceptor is tet, # see if geometry can work out if d not in finished and _typeInfo(d).geometry == 4: if not _tetCheck(d, a, hbondInfo, False): continue if a not in finished and _typeInfo(a).geometry == 4: if d in finished: checks = [] for b in d.primaryNeighbors(): if b.element.number == 1: checks.append(b) else: checks = [d] tetOkay = True for c in checks: if not _tetCheck(a, c, hbondInfo, True): tetOkay = False break if not tetOkay: continue if a in finished: # can d still donate to a? if not _canAccept(d, a, *hbondInfo[a]): # can't continue hbondInfo.setdefault(d, []).append((False, a)) elif d in finished: # can d still donate to a? noProtonsOK = d in aroNs and _numBonds(d) == 2 if not _canDonate(d, a, noProtonsOK, *hbondInfo[d]): # nope continue hbondInfo.setdefault(a, []).append((True, d)) else: addAtoD, addDtoA = _angleCheck(d, a, hbondInfo) if not addAtoD and not addDtoA: continue if addAtoD: hbondInfo.setdefault(d, []).append( (False, a)) if addDtoA: hbondInfo.setdefault(a, []).append( (True, d)) if (a, d) in hbonds: processed.add((a, d)) processedOne = True breakAmbigous = False breakNring = False if hbond not in pruned: _doPrune(hbond, pruned, relBond, processed, prunedBy) for end in hbond: if end in finished or end not in candidates: continue if end not in hbondInfo: # steric clash from other end if debug: print "no hbonds left for", end.oslIdent() continue if debug: print "try to finish", end.oslIdent(), for isAcc, da in hbondInfo[end]: if isAcc: print " accepting from", da.oslIdent(), else: print " donating to", da.oslIdent(), print didFinish = _tryFinish(end, hbondInfo, finished, aroAmines, prunedBy, processed) if not didFinish: continue if debug: print "finished", end.oslIdent() # if this atom is in candidates # then actually add any hydrogens if end in candidates and hbondInfo[end][0]: _attachHydrogens(end, hbondInfo[end][0]) if debug: print "protonated", end.oslIdent() # if ring nitrogen, eliminate # any hbonds where it is an acceptor if isinstance(donors[end],chimera.Ring): for rb in relBond[end]: if rb[1] != end: continue processed.add(rb) if a in seenAmbiguous or d in seenAmbiguous: # revisit previously ambiguous if debug: print "revisiting previous ambiguous" for da in hbond: for rel in relBond[da]: if rel in processed: continue if rel not in pruned: continue reexamine[rel] = True break if breakAmbiguous and not processedOne: breakNring = True breakAmbiguous = not processedOne numFinished = 0 for a in candidates.keys(): if a in finished: numFinished += 1 if debug: print "finished", numFinished, "of", len(candidates), "atoms" replyobj.status("Adding hydrogens to primary aromatic amines\n", blankAfter=0) if debug: print "primary aromatic amines" for a in aroAmines: if a in finished: continue if a not in candidates: continue if debug: print "amine", a.oslIdent() finished[a] = True numFinished += 1 # point protons right toward acceptors; # if also accepting, finish as tet, otherwise as planar acceptFrom = None targets = [] atPos = a.xformCoord() for isAcc, other in hbondInfo.get(a, []): if isAcc: if not acceptFrom: if debug: print "accept from", other.oslIdent() acceptFrom = other.xformCoord() continue if debug: print "possible donate to", other.oslIdent() # find nearest lone pair position on acceptor target = _findTarget(a, atPos, other, not isAcc, hbondInfo, finished) # make sure this proton doesn't form # an angle < 90 degrees with any other bonds badAngle = False for bonded in a.primaryNeighbors(): if chimera.angle(bonded.xformCoord(), atPos, target) < 90.0: badAngle = True break if badAngle: if debug: print "bad angle" continue if targets and chimera.angle(targets[0], atPos, target) < 90.0: if debug: print "bad angle" continue targets.append(target) if len(targets) > 1: break positions = [] for target in targets: vec = target - atPos vec.normalize() positions.append(atPos + vec * bondWithHLength(a, _typeInfo(a).geometry)) if len(positions) < 2: if acceptFrom: geom = 4 knowns = positions + [acceptFrom] coPlanar = None else: geom = 3 knowns = positions coPlanar = [] for bonded in a.primaryNeighbors(): for b2 in bonded.primaryNeighbors(): if a == b2: continue coPlanar.append(b2.xformCoord()) sumVec = chimera.Vector() for x in a.primaryNeighbors(): vec = x.xformCoord() - atPos vec.normalize() sumVec += vec for k in knowns: vec = k - atPos vec.normalize() sumVec += vec sumVec.negate() sumVec.normalize() newPos = bondPositions(atPos, geom, bondWithHLength(a, geom), [nb.xformCoord() for nb in a.primaryNeighbors()] + knowns, coPlanar=coPlanar) positions.extend(newPos) _attachHydrogens(a, positions) if acceptFrom: accVec = acceptFrom - atPos accVec.length = vdwRadius(a) accs = [atPos + accVec] else: accs = [] hbondInfo[a] = (positions, accs) if debug: print "finished", numFinished, "of", len(candidates), "atoms" replyobj.status("Using steric criteria to resolve partial h-bonders\n", blankAfter=0) for a in candidates.keys(): if a in finished: continue if a not in hbondInfo: continue finished[a] = True numFinished += 1 bondingInfo = _typeInfo(a) geom = bondingInfo.geometry numBonds = _numBonds(a) hydsToPosition = bondingInfo.substituents - numBonds openings = geom - numBonds hbInfo = hbondInfo[a] towardAtom = hbInfo[0][1] if debug: print a.oslIdent(), "toward", towardAtom.oslIdent(), towardPos = towardAtom.xformCoord() toward2 = away2 = None atPos = a.xformCoord() if len(hbInfo) > 1: toward2 = hbInfo[1][1].xformCoord() if debug: print "and toward", hbInfo[1][1].oslIdent() elif openings > 3: # okay, we need an "away from" atom just to position # the rotamer away2, dist, nearA = findNearest(atPos, a, [towardAtom], _nearDist) if debug: print "and away from nearest other", if away2: print nearA.oslIdent(), "[%.2f]" % dist else: print "(none)" else: if debug: print "with no other positioning determinant" bondedPos = [] for bonded in a.primaryNeighbors(): bondedPos.append(bonded.xformCoord()) positions = bondPositions(atPos, geom, bondWithHLength(a, geom), bondedPos, toward=towardPos, toward2=toward2, away2=away2) if len(positions) == hydsToPosition: # easy, do them all... _attachHydrogens(a, positions) continue used = {} for isAcc, other in hbInfo: nearest = None otherPos = other.xformCoord() for pos in positions: dsq = (pos - otherPos).sqlength() if not nearest or dsq < nsq: nearest = pos nsq = dsq if nearest in used: continue used[nearest] = isAcc remaining = [] for pos in positions: if pos in used: continue remaining.append(pos) # definitely protonate the positions where we donate... protonate = [] for pos, isAcc in used.items(): if not isAcc: protonate.append(pos) # ... and the "roomiest" remaining positions. rooms = roomiest(remaining, a, _roomDist) needed = hydsToPosition - len(protonate) protonate.extend(rooms[:needed]) # then the least sterically challenged... _attachHydrogens(a, protonate) hbondInfo[a] = (protonate, rooms[needed:]) if debug: print "finished", numFinished, "of", len(candidates), "atoms" replyobj.status("Adding hydrogens to non-h-bonding atoms\n", blankAfter=0) for a in candidates.keys(): if a in finished: continue if a in aroNs: continue finished[a] = True numFinished += 1 bondingInfo = _typeInfo(a) geom = bondingInfo.geometry primaryNeighbors = a.primaryNeighbors() numBonds = len(primaryNeighbors) hydsToPosition = bondingInfo.substituents - numBonds openings = geom - numBonds away = None away2 = None toward = None atPos = a.xformCoord() if debug: print "position", a.oslIdent(), if openings > 2: # okay, we need an "away from" atom for positioning # # if atom is tet with one bond (i.e. possible positions # describe a circle away from the bond), then use the # center of the circle as the test position rather # than the atom position itself if geom == 4 and openings == 3: away, dist, awayAtom = findRotamerNearest(atPos, idatmType[a], a, primaryNeighbors[0], 3.5) else: away, dist, awayAtom = findNearest(atPos, a, [], _nearDist) # actually, if the nearest atom is a metal and we have # a free lone pair, we want to position (the lone # pair) towards the metal if awayAtom and awayAtom.element in metals \ and geom - bondingInfo.substituents > 0: if debug: print "towards metal", awayAtom.oslIdent(), "[%.2f]" % dist, toward = away away = None else: if debug: print "away from", if awayAtom: print awayAtom.oslIdent(), "[%.2f]" % dist, else: print "(none)", if openings > 3 and away is not None: # need another away from away2, dist, nearA = findNearest(atPos, a, [awayAtom], _nearDist) if debug: print "and away from", if nearA: print nearA.oslIdent(), "[%.2f]" % dist, else: print "(none)", if debug: print bondedPos = [] for bonded in primaryNeighbors: bondedPos.append(bonded.xformCoord()) positions = bondPositions(atPos, geom, bondWithHLength(a, geom), bondedPos, toward=toward, away=away, away2=away2) if len(positions) == hydsToPosition: # easy, do them all... _attachHydrogens(a, positions) continue # protonate "roomiest" positions _attachHydrogens(a, roomiest(positions, a, _roomDist)[:hydsToPosition]) if debug: print "finished", numFinished, "of", len(candidates), "atoms" replyobj.status("Deciding aromatic nitrogen protonation\n", blankAfter=0) # protonate one N of an aromatic multiple-N ring if none are yet # protonated for ns in multiNrings.values(): anyBonded = True Npls = [] for n in ns: if _numBonds(n) > 2: break if n not in coordinations \ and _typeInfo(n).substituents == 3: Npls.append(n) else: anyBonded = False if anyBonded: if debug: print ns[0].oslIdent(), "ring already protonated" continue if not Npls: Npls = ns positions = [] for n in Npls: bondedPos = [] for bonded in n.primaryNeighbors(): bondedPos.append(bonded.xformCoord()) positions.append(bondPositions(n.xformCoord(), planar, bondWithHLength(n, planar), bondedPos)[0]) if len(positions) == 1: if debug: print "protonate precise ring", Npls[0].oslIdent() _attachHydrogens(Npls[0], positions) else: if debug: print "protonate roomiest ring", roomiest(positions, Npls, _roomDist)[0][0].oslIdent() _attachHydrogens(*roomiest(positions, Npls, _roomDist)[0]) # now correct IDATM types of these rings... for ns in multiNrings.values(): for n in ns: if len(n.bonds) == 3: n.idatmType = "Npl" else: n.idatmType = "N2" replyobj.status("Hydrogens added\n") if problemAtoms: replyobj.error("Problems adding hydrogens to %d atom(s); see Reply Log for details\n" % len(problemAtoms)) from chimera.misc import chimeraLabel replyobj.info("Did not protonate following atoms:\n%s\n" % "\t".join(map(chimeraLabel, problemAtoms))) addAtoms = None typeInfo4Atom = namingSchemas = hydrogenTotals = idatmType \ = aroAmines = inversionCache = coordinations = None
def hydPositions(heavy, includeLonePairs=False): """Return list of positions for hydrogens attached to this atom. If a hydrogen could be in one of several positions, don't return any of those. """ # first, find known attached atoms bondedHeavys = [] hyds = [] for atom in heavy.primaryNeighbors(): if atom.element.number > 1: bondedHeavys.append(atom) else: hyds.append(atom) # convert to Points hydLocs = [] for hyd in hyds: hydLocs.append(hyd.xformCoord()) if hydLocs and not includeLonePairs: # explicit hydrogens "win" over atom types return hydLocs if typeInfo.has_key(heavy.idatmType): info = typeInfo[heavy.idatmType] geom = info.geometry if includeLonePairs: subs = geom else: subs = info.substituents bondedLocs = hydLocs[:] for bHeavy in bondedHeavys: bondedLocs.append(bHeavy.xformCoord()) else: return hydLocs knownSubs = len(bondedLocs) if knownSubs >= subs or knownSubs == 0: return hydLocs # above eliminates 'single' geometry if knownSubs == 1 and geom == tetrahedral: # rotamer return hydLocs maxSubs = geom if maxSubs - subs > 0: # the "empty" bond could be anywhere return hydLocs heavyLoc = heavy.xformCoord() bondLen = Element.bondLength(heavy.element, Element(1)) if geom == planar: coPlanar = [] for bHeavy in bondedHeavys: try: bhGeom = typeInfo[bHeavy.idatmType].geometry except KeyError: bhGeom = None if bhGeom != planar: continue for atom in bHeavy.primaryNeighbors(): if atom != heavy: coPlanar.append(atom.xformCoord()) else: coPlanar = None hydLocs = hydLocs + bondPositions( heavyLoc, geom, bondLen, bondedLocs, coPlanar=coPlanar) return hydLocs
def _resolveAnilene(donor, acceptor, aroAmines, hbondInfo): # donor/acceptor are currently ambiguous; if donor and/or acceptor are # anilenes, see if they can be determined to prefer to donate/accept # respectively (if a proton and/or lone pair has been added, see if # vector to other atom is more planar or more tetrahedral) if donor in aroAmines and donor in hbondInfo: toward = None for isAcc, da in hbondInfo[donor]: if isAcc: return True if toward: break toward = da.xformCoord() else: # one proton attached donorPos = donor.xformCoord() acceptorPos = acceptor.xformCoord() attached = [ donor.primaryNeighbors()[0].xformCoord()] planars = bondPositions(donorPos, 3, N_H, attached, toward=toward) planarDist = None for planar in planars: dist = (acceptorPos - planar).sqlength() if planarDist is None or dist < planarDist: planarDist = dist for tetPos in bondPositions(donorPos, 4, N_H, attached): if (tetPos - acceptorPos).sqlength() \ < planarDist: # closer to tet position, # prefer acceptor-like behavior return False if debug: print "resolving", donor.oslIdent(), "->", acceptor.oslIdent(), "because of donor" return True if acceptor in aroAmines and acceptor in hbondInfo: toward = None for isAcc, da in hbondInfo[acceptor]: if isAcc: return False if toward: break toward = da.xformCoord() else: # one proton attached donorPos = donor.xformCoord() acceptorPos = acceptor.xformCoord() attached = [acceptor.primaryNeighbors()[0].xformCoord()] planars = bondPositions(acceptorPos, 3, N_H, attached, toward=toward) planarDist = None for planar in planars: dist = (acceptorPos - planar).sqlength() if planarDist is None or dist < planarDist: planarDist = dist for tetPos in bondPositions(acceptorPos, 4, N_H, attached): if (tetPos - donorPos).sqlength() \ < planarDist: # closer to tet position, # prefer acceptor-like behavior if debug: print "resolving", donor.oslIdent(), "->", acceptor.oslIdent(), "because of acceptor" return True return False return False
def donUpsilonTau(donor, donorHyds, acceptor, sp2Or2, sp2OupsilonLow, sp2OupsilonHigh, sp2Otheta, sp2Otau, sp3Or2, sp3OupsilonLow, sp3OupsilonHigh, sp3Otheta, sp3Otau, sp3Ophi, sp3Nr2, sp3NupsilonLow, sp3NupsilonHigh, sp3Ntheta, sp3Ntau, sp3NupsilonN, genR2, genUpsilonLow, genUpsilonHigh, genTheta, tauSym): if base.verbose: print "donUpsilonTau" accType = acceptor.idatmType if not typeInfo.has_key(accType): return 0 geom = typeInfo[accType].geometry element = acceptor.element.name if element == 'O' and geom == planar: if base.verbose: print "planar O" return testUpsilonTauAcceptor(donor, donorHyds, acceptor, sp2Or2, sp2OupsilonLow, sp2OupsilonHigh, sp2Otheta, sp2Otau, tauSym) elif element == 'O' and geom == tetrahedral \ or element == 'N' and geom == planar: if base.verbose: print "planar N or tet O" return testUpsilonTauAcceptor(donor, donorHyds, acceptor, sp3Or2, sp3OupsilonLow, sp3OupsilonHigh, sp3Otheta, sp3Otau, tauSym) elif element == 'N' and geom == tetrahedral: if base.verbose: print "tet N" # test upsilon at the N # see if lone pairs point at the donor bondedPos = [] for bonded in acceptor.primaryNeighbors(): bondedPos.append(bonded.xformCoord()) if len(bondedPos) > 1: ap = acceptor.xformCoord() dp = donor.xformCoord() lonePairs = bondPositions(ap, tetrahedral, 1.0, bondedPos) for lp in lonePairs: upPos = ap - (lp - ap) ang = angle(upPos, ap, dp) if ang >= sp3NupsilonN: if base.verbose: print "upsilon(N) okay" \ " (%g >= %g)" % (ang, sp3NupsilonN) break else: if base.verbose: print "all upsilon(N) failed (< %g)" % ( sp3NupsilonN) return 0 elif base.verbose: print "lone pair positions indeterminate at N;" \ "upsilon(N) default okay" return testUpsilonTauAcceptor(donor, donorHyds, acceptor, sp3Nr2, sp3NupsilonLow, sp3NupsilonHigh, sp3Ntheta, sp3Ntau, tauSym) else: if base.verbose: print "generic acceptor" if acceptor.element.name == "S": genR2 = sulphurCompensate(genR2) return testUpsilonTauAcceptor(donor, donorHyds, acceptor, genR2, genUpsilonLow, genUpsilonHigh, genTheta, None, None) if base.verbose: print "failed criteria" return 0
def donThetaTau(donor, donorHyds, acceptor, sp2Orp2, sp2Otheta, sp3Orp2, sp3Otheta, sp3Ophi, sp3Nrp2, sp3Ntheta, sp3Nupsilon, genRp2, genTheta, isWater=0): # 'isWater' only for hydrogenless water if base.verbose: print "donThetaTau" if len(donorHyds) == 0 and not isWater: if base.verbose: print "No hydrogens; default failure" return 0 ap = acceptor.xformCoord() dp = donor.xformCoord() accType = acceptor.idatmType if not typeInfo.has_key(accType): if base.verbose: print "Unknown acceptor type failure" return 0 geom = typeInfo[accType].geometry element = acceptor.element.name if element == 'O' and geom == planar: if base.verbose: print "planar O" for hydPos in donorHyds: if sqdistance(hydPos, ap) <= sp2Orp2: break else: if not isWater: if base.verbose: print "dist criteria failed (all > %g)"\ % sqrt(sp2Orp2) return 0 theta = sp2Otheta elif element == 'O' and geom == tetrahedral \ or element == 'N' and geom == planar: if base.verbose: print "planar N or tet O" for hydPos in donorHyds: if sqdistance(hydPos, ap) <= sp3Orp2: break else: if not isWater: if base.verbose: print "dist criteria failed (all > %g)"\ % sqrt(sp3Orp2) return 0 theta = sp3Otheta # only test phi for acceptors with two bonded atoms if len(acceptor.primaryBonds()) == 2: if base.verbose: print "testing donor phi" bonded = acceptor.primaryNeighbors() phiPlane, basePos = getPhiPlaneParams(acceptor, bonded[0], bonded[1]) if not testPhi(donor.xformCoord(), ap, basePos, phiPlane, sp3Ophi): return 0 elif element == 'N' and geom == tetrahedral: if base.verbose: print "tet N" for hydPos in donorHyds: if sqdistance(hydPos, ap) <= sp3Nrp2: break else: if not isWater: if base.verbose: print "dist criteria failed (all > %g)"\ % sqrt(sp3Nrp2) return 0 theta = sp3Ntheta # test upsilon against lone pair directions bondedPos = [] for bonded in acceptor.primaryNeighbors(): bondedPos.append(bonded.xformCoord()) lpPos = bondPositions(ap, geom, 1.0, bondedPos) if lpPos: # fixed lone pair positions for lp in bondPositions(ap, geom, 1.0, bondedPos): # invert position so that we are # measuring angles correctly ang = angle(dp, ap, ap - (lp - ap)) if ang > sp3Nupsilon: if base.verbose: print "acceptor upsilon okay"\ " (%g > %g)" % ( ang, sp3Nupsilon) break else: if base.verbose: print "all acceptor upsilons failed"\ " (< %g)" % sp3Nupsilon return 0 # else: indefinite lone pair positions; default okay else: if base.verbose: print "generic acceptor" if acceptor.element.name == "S": genRp2 = sulphurCompensate(genRp2) for hydPos in donorHyds: if sqdistance(hydPos, ap) <= genRp2: break else: if base.verbose: print "dist criteria failed (all > %g)" % sqrt( genRp2) return 0 theta = genTheta if base.verbose: print "dist criteria OK" return testTheta(dp, donorHyds, ap, theta)