def _canDonate(donor, acceptor, noProtonsOK, protons, lonePairs): # donor is fixed; can it donate to acceptor? if not lonePairs: return True if not protons: if noProtonsOK: if debug: print "can't still donate; no protons" return False raise ValueError, "No protons for %s to accept from" % ( acceptor.oslIdent()) accPos = acceptor.xformCoord() hDist, h = min(map(lambda xyz: ((xyz - accPos).sqlength(), xyz), protons)) lpDist = min(map(lambda xyz: (xyz - accPos).sqlength(), lonePairs)) # besides a proton being closest, it must be sufficiently pointed # towards the acceptor if hDist >= lpDist: from math import sqrt if debug: print "can't still donate; h dist (%g) >= lp dist (%g)" % ( sqrt(hDist), sqrt(lpDist)) elif chimera.angle(h, donor.xformCoord(), accPos) >= _testAngles[ typeInfo4Atom[donor].geometry]: if debug: print "can't still donate; angle (%g) >= test angle (%g)" % ( chimera.angle(h, donor.xformCoord(), accPos), _testAngles[ typeInfo4Atom[donor].geometry]) return hDist < lpDist and chimera.angle(h, donor.xformCoord(), accPos) < _testAngles[ typeInfo4Atom[donor].geometry]
def angle(self, items): from Axes import Axis from Planes import Plane numAxes = len([i for i in items if isinstance(i, Axis)]) numPlanes = len([i for i in items if isinstance(i, Plane)]) if numAxes == 2: axis1, axis2 = items angle = chimera.angle(axis1.xformDirection(), axis2.xformDirection()) if angle > 90.0: angle = 180.0 - angle elif numAxes == numPlanes == 1: if isinstance(items[0], Axis): axis, plane = items else: plane, axis = items angle = chimera.angle(axis.xformDirection(), plane.xformNormal()) angle = abs(90.0 - angle) elif numPlanes == 2: plane1, plane2 = items angle = chimera.angle(plane1.xformNormal(), plane2.xformNormal()) if angle > 90.0: angle = 180.0 - angle else: raise ValueError("angle calculation not implemented") return angle
def accGeneric(donor, donorHyds, acceptor, r2, minAngle): if base.verbose: print "generic acceptor" 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" ap = acceptor.xformCoord() dp = donor.xformCoord() for bonded in acceptor.primaryNeighbors(): bp = bonded.xformCoord() if base.verbose: print "angle: %g" % angle(bp, ap, dp) ang = angle(bp, ap, dp) if ang < minAngle: if base.verbose: print "angle too sharp (%g < %g)" % (ang, minAngle) return 0 if base.verbose: print "angle(s) okay (all > %g)" % minAngle return 1
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 _angleLabel(self, atoms): pts = tuple([a.xformCoord() for a in atoms]) if len(pts) == 3: val = chimera.angle(*pts) else: val = chimera.dihedral(*pts) return "%.*f" % (prefs[ANGLE_PRECISION], val)
def order_retriever(self, dummies_xyz): """ Base class method to order dummies for types. Logic: - For each dummy we look for the one being at the opposite side of the metal by looking for a DZ-MET_DZ angle of 180. Then we appned both together on the list. """ new_order = [] for dummy in dummies_xyz: if dummy not in new_order: new_order.append(dummy) for i in range(0, len(dummies_xyz)): angle = chimera.angle(dummy, self.metal.labelCoord(), dummies_xyz[i]) if abs(angle - 180) < 0.01 and dummies_xyz[i] not in new_order: new_order.append(dummies_xyz[i]) break return new_order
def _axisDistance(self, axis, infinite=False): from chimera import angle, cross, Plane # shortest distance between lines is perpendicular to both... sDir = self.xformDirection() aDir = axis.xformDirection() if angle(sDir, aDir) in [0.0, 180.0]: # parallel return self._axisEndsDist(axis) shortDir = cross(sDir, aDir) # can use analytically shortest dist only if each axis # penetrates the plane formed by the other axis and the # perpendicular if not infinite: for a1, a2 in [(axis, self), (self, axis)]: normal = cross(a1.xformDirection(), shortDir) plane = Plane(a1.xformCenter(), normal) d1 = plane.distance(a2.xformCenter() + a2.xformDirection() * a2.extents[0]) d2 = plane.distance(a2.xformCenter() + a2.xformDirection() * a2.extents[1]) if cmp(d1, 0.0) == cmp(d2, 0.0): # both ends on same side of plane return self._axisEndsDist(axis) # D is perpendicular distance to origin d1 = Plane(self.xformCenter(), shortDir).equation()[3] d2 = Plane(axis.xformCenter(), shortDir).equation()[3] return abs(d1 - d2)
def rotate(molecule, at, alpha): if len(at) == 3: try: a1, a2, a3 = [a.coord() for a in at] except AttributeError: a1, a2, a3 = at axis_a = a1 - a2 axis_b = a3 - a2 delta = chimera.angle(a1, a2, a3) - alpha axis = chimera.cross(axis_a, axis_b) if axis.data() == (0.0, 0.0, 0.0): axis = chimera.cross(axis_a, axis_b + chimera.Vector(1, 0, 0)) logger.warning("Had to choose arbitrary normal vector") pivot = a2 elif len(at) == 4: try: a1, a2, a3, a4 = [a.coord() for a in at] except AttributeError: a1, a2, a3, a4 = at axis = a3 - a2 delta = chimera.dihedral(a1, a2, a3, a4) - alpha pivot = a3 else: raise ValueError( "Atom list must contain 3 (angle) or 4 (dihedral) atoms only") r = X.translation(pivot - ZERO) # move to origin r.multiply(X.rotation(axis, -delta)) # rotate r.multiply(X.translation(ZERO - pivot)) # return to orig pos for a in molecule.atoms: a.setCoord(r.apply(a.coord()))
def evaluate(self, ind): atoms_coords = [a.xformCoord() for a in self.probes(ind)] try: angle = chimera.angle(*atoms_coords) except TypeError: # four atoms, means dihedral angle = chimera.dihedral(*atoms_coords) if self.threshold == 'planar': return abs(math.sin(math.radians(angle))) return abs(self.threshold - angle.real)
def _angleCheck(d, a, hbondInfo): addAtoD = addDtoA = True # are the protons/lps already added to the # donor pointed toward the acceptor? if d in hbondInfo: geom = _typeInfo(d).geometry for isAcc, da in hbondInfo[d]: angle = chimera.angle(da.xformCoord(), d.xformCoord(), a.xformCoord()) if angle > _testAngles[geom]: continue if isAcc: # lone pair pointing toward acceptor; # won't work addAtoD = addDtoA = False if debug: print "can't donate; lone pair (to %s) pointing toward acceptor (angle %g)" % (da.oslIdent(), angle) break addAtoD = False if debug: print "donor already pointing (angle %g) towards acceptor (due to %s)" % (angle, da.oslIdent()) if not addAtoD and not addDtoA: return addAtoD, addDtoA if a in hbondInfo: geom = _typeInfo(a).geometry for isAcc, da in hbondInfo[a]: angle = chimera.angle(da.xformCoord(), a.xformCoord(), d.xformCoord()) if angle > _testAngles[geom]: continue if not isAcc: # proton pointing toward donor; won't work if debug: print "can't accept; proton (to %s) pointing too much toward donor (angle %g)" % (da.oslIdent(), angle) addAtoD = addDtoA = False break addDtoA = False if debug: print "acceptor already pointing too much (angle %g) towards donor (due to %s)" % (angle, da.oslIdent()) return addAtoD, addDtoA
def interpInternal(c0map, c1map, f, cs, a0, a1, a2, a3): """Computer coordinate of atom a0 by interpolating dihedral angle defined by atoms (a0, a1, a2, a3)""" import chimera c00 = c0map[a0] c01 = c0map[a1] c02 = c0map[a2] c03 = c0map[a3] length0 = c00.distance(c01) angle0 = chimera.angle(c00, c01, c02) dihed0 = chimera.dihedral(c00, c01, c02, c03) c10 = c1map[a0] c11 = c1map[a1] c12 = c1map[a2] c13 = c1map[a3] length1 = c10.distance(c11) angle1 = chimera.angle(c10, c11, c12) dihed1 = chimera.dihedral(c10, c11, c12, c13) length = length0 + (length1 - length0) * f angle = angle0 + (angle1 - angle0) * f ddihed = dihed1 - dihed0 if ddihed > 180: ddihed -= 360 elif ddihed < -180: ddihed += 360 dihed = dihed0 + ddihed * f c1 = a1.coord(cs) c2 = a2.coord(cs) c3 = a3.coord(cs) from chimera import molEdit try: c0 = molEdit.findPt(c1, c2, c3, length, angle, dihed) except ValueError: print `a1`, a1 print `a2`, a2 print `a3`, a3 raise a0.setCoord(c0, cs)
def _lenAngle(new, n1, n2, tmplMap, bondCache, angleCache): from chimera import distance, angle bondKey = (n1, new) angleKey = (n2, n1, new) try: bl = bondCache[bondKey] ang = angleCache[angleKey] except KeyError: n2pos = tmplMap[n2].coord() n1pos = tmplMap[n1].coord() newpos = tmplMap[new].coord() bondCache[bondKey] = bl = distance(newpos, n1pos) angleCache[angleKey] = ang = angle(newpos, n1pos, n2pos) return bl, ang
def testTheta(dp, donorHyds, ap, theta): if len(donorHyds) == 0: if base.verbose: print "no hydrogens for theta test; default accept" return 1 for hydPos in donorHyds: ang = angle(ap, hydPos, dp) if ang >= theta: if base.verbose: print "theta okay (%g >= %g)" % (ang, theta) return 1 if base.verbose: print "theta failure (%g < %g)" % (ang, theta) return 0
def _canAccept(donor, acceptor, protons, lonePairs): # acceptor is fixed; can it accept from donor? if not protons: return True if not lonePairs: raise ValueError("No lone pairs on %s for %s to donate to" % ( acceptor, donor)) donPos = donor.xformCoord() hDist = min(map(lambda xyz: (xyz - donPos).sqlength(), protons)) lpDist, lp = min(map(lambda xyz: ((xyz - donPos).sqlength(), xyz), lonePairs)) # besides a lone pair being closest, it must be sufficiently pointed # towards the donor if lpDist >= hDist: from math import sqrt if debug: print "can't still accept; lp dist (%g) >= h dist (%g)" % (sqrt(lpDist), sqrt(hDist)) elif chimera.angle(lp, acceptor.xformCoord(), donPos) >= _testAngles[ typeInfo4Atom[acceptor].geometry]: if debug: print "can't still accept; angle (%g) >= test angle (%g)" % ( chimera.angle(lp, acceptor.xformCoord(), donPos), _testAngles[ typeInfo4Atom[acceptor].geometry]) return lpDist < hDist and chimera.angle(lp, acceptor.xformCoord(), donPos) < _testAngles[ typeInfo4Atom[acceptor].geometry]
def testThetaTau(dp, donorHyds, ap, pp, upsilonLow, upsilonHigh, theta): if pp: upsilonHigh = 0 - upsilonHigh upsilon = angle(pp, ap, dp) if upsilon < upsilonLow or upsilon > upsilonHigh: if base.verbose: print "upsilon (%g) failed (%g-%g)" % ( upsilon, upsilonLow, upsilonHigh) return 0 else: if base.verbose: print "can't determine upsilon; default okay" if base.verbose: print "upsilon okay" return testTheta(dp, donorHyds, ap, theta)
def _tetCheck(tet, partner, hbondInfo, tetAcc): """Check if tet can still work""" tetPos = tet.xformCoord() partnerPos = partner.xformCoord() bonded = tet.primaryNeighbors() tetInfo = hbondInfo.get(tet, []) towards = [] # if there is a real bond to the tet, we want to check the # dihedral to the new position (using a 120 angle) rather than # the angle (109.5) since the vector from the tet to the not- # yet-added positions is probably not directly along the future bond if bonded: if debug: print "checking dihedral" chkFunc = lambda op, pp=partnerPos, tp=tetPos, \ bp=bonded[0].xformCoord(): chimera.dihedral( pp, tp, bp, op) / 30.0 else: if debug: print "checking angle" chkFunc = lambda op, pp=partnerPos, tp=tetPos: chimera.angle( pp, tp, op) / 27.375 for isAcc, other in tetInfo: if isAcc == tetAcc: # same "polarity"; pointing towards is good result = True else: # opposite "polarity"; pointing towards is bad result = False angleGroup = int(chkFunc(other.xformCoord())) if angleGroup in [1,2,5,6]: # in the tetrahedral "dead zones" if debug: print "tetCheck for", tet.oslIdent(), print "; dead zone for", other.oslIdent() return False if angleGroup == 0: if debug: print "tetCheck for", tet.oslIdent(), print "; pointing towards", other.oslIdent(), print "returning", result return result towards.append(other.xformCoord()) # further tests only for 2 of 4 filled... if bonded or len(towards) != 2: return True return _tet2check(tetPos, towards[0], towards[1], partnerPos)
def formDihedral(resBud, real1, tmplRes, a, b): res = resBud.residue inres = filter(lambda a, rb=resBud, r=res: a.residue == r and a != rb, real1.neighbors) if real1.residue != res or len(inres) < 1: raise AssertionError, "Can't form in-residue dihedral for" \ " %s of residue %s" % (bud, res.oslIdent()) real2 = inres[0] xyz0, xyz1, xyz2 = map(lambda a, tr=tmplRes: tr.atomsMap[a.name].coord(), (resBud, real1, real2)) xyz = a.coord() blen = b.length() ang = angle(xyz, xyz0, xyz1) dihed = dihedral(xyz, xyz0, xyz1, xyz2) return addDihedralAtom(a.name, a.element, resBud, real1, real2, blen, ang, dihed)
def test_include_dummies(element, file, charge, geom, dummies_xyz, Model=Model): # Right Values ANGLE = { 'tetrahedral': [ 60, ], 'square planar': [90, 45], 'square pyramid': [90, 45], 'octahedron': [90, 60, 45], } # Create metal instance metal, metal_class = create_metal_class(element=element, charge=charge, geom=geom, file=file) # Initialize variables metal_class.dummies_xyz = dummies_xyz Model.geometry = geom # Func to test Model.include_dummies(metal_class) # Selecting the included atoms number_of_dummies = len(dummies_xyz) atoms_to_select = ','.join( ['D' + str(i) for i in range(1, number_of_dummies + 1)]) rc("sel ::" + str(metal.residue.type) + '@' + atoms_to_select) dummies = chimera.selection.currentAtoms() # Eval Angles between them depending geometry for i in range(0, number_of_dummies - 2): angle = chimera.angle(dummies[i].labelCoord(), dummies[i + 1].labelCoord(), dummies[i + 2].labelCoord()) assert (int(round(angle)) in ANGLE[geom]) # Eval Bonds between Dummy&Metal center for i in range(0, number_of_dummies): bond = chimera.distance(dummies[i].labelCoord(), metal.labelCoord()) assert (abs(bond - 0.9) < 0.001)
def testPhi(dp, ap, bp, phiPlane, phi): if phiPlane: normal = cross(phiPlane[1] - phiPlane[0], phiPlane[2] - phiPlane[1]) normal.normalize() D = normal * phiPlane[1].toVector() bproj = project(bp, normal, D) aproj = project(ap, normal, D) dproj = project(dp, normal, D) ang = angle(bproj, aproj, dproj) if ang < phi: if base.verbose: print "phi criteria failed (%g < %g)" % (ang, phi) return 0 if base.verbose: print "phi criteria OK (%g >= %g)" % (ang, phi) else: if base.verbose: print "phi criteria irrelevant" return 1
def _pruneCheck(pivotAtom, goldHBond, testHBond): geom = _typeInfo(pivotAtom).geometry if geom < 2: return False if geom < 4 and _numBonds(pivotAtom) > 0: return False if goldHBond[0] == pivotAtom: ga = goldHBond[1] else: ga = goldHBond[0] if testHBond[0] == pivotAtom: ta = testHBond[1] else: ta = testHBond[0] angle = chimera.angle(ga.xformCoord(), pivotAtom.xformCoord(), ta.xformCoord()) fullAngle = _testAngles[geom] * 3 while angle > fullAngle / 2.0: angle -= fullAngle angle = abs(angle) return angle > fullAngle / 4.0
def addDihedralBond(a1, a2, length, angleInfo, dihedInfo): """Make bond between two models. The models will be combined and the originals closed. The new model will be opened in the same id/subid as the non-moving model. a1/a2 are atoms in different models. a2 and atoms in its model will be moved to form the bond. 'length' is the bond length. 'angleInfo' is a two-tuple of sequence of three atoms and an angle that the three atoms should form. 'dihedInfo' is like 'angleInfo', but 4 atoms. angleInfo/dihedInfo can be None if insufficient atoms """ if a1.molecule == a2.molecule: raise ValueError("Atoms to be bonded must be in different models") # first, get the distance correct from chimera import Xform, cross, angle, Point dvector = a1.xformCoord() - a2.xformCoord() dvector.length = dvector.length + length openState = a2.molecule.openState openState.globalXform(Xform.translation(dvector)) # then angle if angleInfo: atoms, angleVal = angleInfo p1, p2, p3 = [a.xformCoord() for a in atoms] axis = cross(p1-p2, p2-p3) curAngle = angle(p1, p2, p3) delta = angleVal - curAngle v2 = p2 - Point(0.0, 0.0, 0.0) trans1 = Xform.translation(v2) v2.negate() trans2 = Xform.translation(v2) trans1.multiply(Xform.rotation(axis, delta)) trans1.multiply(trans2) openState.globalXform(trans1)
def addDihedralBond(a1, a2, length, angleInfo, dihedInfo): """Make bond between two models. The models will be combined and the originals closed. The new model will be opened in the same id/subid as the non-moving model. a1/a2 are atoms in different models. a2 and atoms in its model will be moved to form the bond. 'length' is the bond length. 'angleInfo' is a two-tuple of sequence of three atoms and an angle that the three atoms should form. 'dihedInfo' is like 'angleInfo', but 4 atoms. angleInfo/dihedInfo can be None if insufficient atoms """ if a1.molecule == a2.molecule: raise ValueError("Atoms to be bonded must be in different models") # first, get the distance correct from chimera import Xform, cross, angle, Point dvector = a1.xformCoord() - a2.xformCoord() dvector.length = dvector.length + length openState = a2.molecule.openState openState.globalXform(Xform.translation(dvector)) # then angle if angleInfo: atoms, angleVal = angleInfo p1, p2, p3 = [a.xformCoord() for a in atoms] axis = cross(p1 - p2, p2 - p3) curAngle = angle(p1, p2, p3) delta = angleVal - curAngle v2 = p2 - Point(0.0, 0.0, 0.0) trans1 = Xform.translation(v2) v2.negate() trans2 = Xform.translation(v2) trans1.multiply(Xform.rotation(axis, delta)) trans1.multiply(trans2) openState.globalXform(trans1)
def measure_curvature(mset): atoms = [m.atom for m in mset.markers()] a2 = [a for a in atoms if len(a.bonds) == 2] kmax = kmin = None ksum = 0 for a in a2: b1, b2 = a.bonds s = 0.5 * (b1.length() + b2.length()) from chimera import angle t = angle(a.coord() - b1.otherAtom(a).coord(), b2.otherAtom(a).coord() - a.coord()) from math import pi trad = t * pi / 180 k = trad / s if kmin is None or k < kmin: kmin = k if kmax is None or k > kmax: kmax = k ksum += k kave = ksum / len(a2) if len(a2) > 0 else None return kave, kmin, kmax
def search_for_ligands(metal): """ Search for ligands near by the metal center excluding candidates through the next steps: 1-How many electrons on the outter shell 2-Metal-Ligand distance 3-Geometry angles 4-Exclude Hydrogens and other atoms Parameters: ----------- metal: chimera metal object Output: ------- chimera ligands object """ # Extracted directly from: # Metal Geom Chimera data = [] coordLim = 4.0 from numpy import array atoms = array(metal.molecule.atoms) from _multiscale import get_atom_coordinates as gac from _closepoints import find_close_points, BOXES_METHOD ignore, close = find_close_points(BOXES_METHOD, gac(array([metal])), gac(atoms), coordLim) candidates = list(set(atoms[close])) mcrd = metal.coord() candidates.sort(lambda a1, a2: cmp(a1.coord().sqdistance(mcrd), a2.coord().sqdistance(mcrd))) exclude = [] userIncluded = [] for candidate in candidates: if candidate == metal: continue if candidate in exclude: continue if candidate not in userIncluded: valence = (candidate.element.number - 2) % 8 if valence < 5 or candidate.element.number == 1: continue if candidate.coord().distance(mcrd) > coordLim: break if candidate not in metal.bondsMap: from chimera import angle from chimera.idatm import typeInfo angleOK = True try: cnGeom = typeInfo[candidate.idatmType].geometry except KeyError: cnGeom = 0 else: if len(candidate.primaryNeighbors()) == cnGeom: # no lone pairs, no possibility of deprotonation continue angleCutoff = [0.0, 72.98, 120.0, 80.0, 72.98][cnGeom] for cnb in candidate.neighbors: if cnb == metal: continue if angle(cnb.coord(), candidate.coord(), metal.coord()) < angleCutoff: angleOK = False break if not angleOK: continue data.append(candidate) return data
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 testUpsilonTauAcceptor(donor, donorHyds, acceptor, r2, upsilonLow, upsilonHigh, theta, tau, tauSym): dc = donor.xformCoord() ac = acceptor.xformCoord() D2 = dc.sqdistance(ac) if D2 > r2: if base.verbose: print "dist criteria failed (%g > %g)" % (sqrt(D2), sqrt(r2)) return 0 upsilonHigh = 0 - upsilonHigh heavys = filter(lambda a: a.element.number > 1, donor.primaryNeighbors()) if len(heavys) != 1: raise AtomTypeError("upsilon tau donor (%s) not bonded to" " exactly one heavy atom" % donor.oslIdent()) ang = angle(heavys[0].xformCoord(), dc, ac) if ang < upsilonLow or ang > upsilonHigh: if base.verbose: print "upsilon criteria failed (%g < %g or %g > %g)" % ( ang, upsilonLow, ang, upsilonHigh) return 0 if base.verbose: print "upsilon criteria OK (%g < %g < %g)" % (upsilonLow, ang, upsilonHigh) dp = dc ap = ac if not testTheta(dp, donorHyds, ap, theta): return 0 if tau is None: if base.verbose: print "tau test irrelevant" return 1 # sulfonamides and phosphonamides can have bonded NH2 groups that # are planar enough to be declared Npl, so use the hydrogen # positions to determine planarity if possible if tauSym == 4: bondedPos = hydPositions(donor) else: # since we expect tetrahedral hydrogens to be oppositely # aligned from the attached tetrahedral center, # we can't use their positions for tau testing bondedPos = [] if 2 * len(bondedPos) != tauSym: bondedPos = hydPositions(heavys[0], includeLonePairs=True) donorEquiv = donor.allLocations() for b in heavys[0].primaryNeighbors(): if b in donorEquiv or b.element.number < 2: continue bondedPos.append(b.xformCoord()) if not bondedPos: if base.verbose: print "tau indeterminate; default okay" return 1 if 2 * len(bondedPos) != tauSym: raise AtomTypeError("Unexpected tau symmetry (%d," " should be %d) for donor %s" % ( 2 * len(bondedPos), tauSym, donor.oslIdent())) normal = heavys[0].xformCoord() - dp normal.normalize() if tau < 0.0: test = lambda ang, t=tau: ang <= 0.0 - t else: test = lambda ang, t=tau: ang >= t projAccPos = project(ap, normal, 0.0) projDonPos = project(dp, normal, 0.0) for bpos in bondedPos: projBpos = project(bpos, normal, 0.0) ang = angle(projAccPos, projDonPos, projBpos) if test(ang): if tau < 0.0: if base.verbose: print "tau okay (%g < %g)" % (ang, -tau) return 1 else: if tau > 0.0: if base.verbose: print "tau too small (%g < %g)" % (ang, tau) return 0 if tau < 0.0: if base.verbose: print "all taus too big (> %g)" % -tau return 0 if base.verbose: print "all taus acceptable (> %g)" % tau return 1
import chimera from chimera import Plane, Xform, cross, Vector, Point m = chimera.openModels.list()[0] b = chimera.selection.currentBonds()[0] bondVec = b.atoms[0].coord() - b.atoms[1].coord() bondVec.normalize() axis = Vector(1.0, 0.0, 0.0) crossProd = cross(axis, bondVec) if crossProd.sqlength() > 0: from math import acos, degrees xform = Xform.rotation(crossProd, degrees(acos(axis * bondVec))) xform.invert() else: xform = Xform.identity() m.openState.xform = xform # okay, that puts the bond parallel to the X axis, now swing the plane of # the rest of the molecule into the xy plane... molPlane = Plane([a.xformCoord() for a in m.atoms[:3]]) angle = chimera.angle(molPlane.normal, Vector(0, 0, -1)) xform2 = Xform.rotation(b.atoms[0].xformCoord()-b.atoms[1].xformCoord(), angle) xform2.multiply(xform) m.openState.xform = xform2
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)
import chimera from chimera import Plane, Xform, cross, Vector, Point m = chimera.openModels.list()[0] b = chimera.selection.currentBonds()[0] bondVec = b.atoms[0].coord() - b.atoms[1].coord() bondVec.normalize() axis = Vector(1.0, 0.0, 0.0) crossProd = cross(axis, bondVec) if crossProd.sqlength() > 0: from math import acos, degrees xform = Xform.rotation(crossProd, degrees(acos(axis * bondVec))) xform.invert() else: xform = Xform.identity() m.openState.xform = xform # okay, that puts the bond parallel to the X axis, now swing the plane of # the rest of the molecule into the xy plane... molPlane = Plane([a.xformCoord() for a in m.atoms[:3]]) angle = chimera.angle(molPlane.normal, Vector(0, 0, -1)) xform2 = Xform.rotation(b.atoms[0].xformCoord() - b.atoms[1].xformCoord(), angle) xform2.multiply(xform) m.openState.xform = xform2
def donGeneric(donor, donorHyds, acceptor, sp2Orp2, sp3Orp2, sp3Nrp2, sp2Or2, sp3Or2, sp3Nr2, genRp2, genR2, minHydAngle, minBondedAngle): if base.verbose: print "donGeneric" dc = donor.xformCoord() ac = acceptor.xformCoord() 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" r2 = sp2Or2 rp2 = sp2Orp2 elif element == 'O' and geom == tetrahedral \ or element == 'N' and geom == planar: if base.verbose: print "planar N or tet O" r2 = sp3Or2 rp2 = sp3Orp2 elif element == 'N' and geom == tetrahedral: if base.verbose: print "tet N" r2 = sp3Nr2 rp2 = sp3Nrp2 else: if base.verbose: print "generic acceptor" if acceptor.element.name == "S": r2 = sulphurCompensate(genR2) minBondedAngle = minBondedAngle - 9 r2 = genR2 rp2 = genRp2 ap = acceptor.xformCoord() dp = donor.xformCoord() if len(donorHyds) == 0: D2 = dc.sqdistance(ac) if D2 > r2: if base.verbose: print "dist criteria failed (%g > %g)" % ( sqrt(D2), sqrt(r2)) return 0 else: for hydPos in donorHyds: if sqdistance(hydPos, ap) < rp2: break else: if base.verbose: print "hyd dist criteria failed (all >= %g)" % ( sqrt(rp2)) return 0 if base.verbose: print "dist criteria OK" for bonded in donor.primaryNeighbors(): if bonded.element.number <= 1: continue bp = bonded.xformCoord() ang = angle(bp, dp, ap) if ang < minBondedAngle: if base.verbose: print "bonded angle too sharp (%g < %g)" % ( ang, minBondedAngle) return 0 if len(donorHyds) == 0: if base.verbose: print "No specific hydrogen positions; default accept" return 1 for hydPos in donorHyds: ang = angle(dp, hydPos, ap) if ang >= minHydAngle: if base.verbose: print "hydrogen angle okay (%g >= %g)" % ( ang, minHydAngle) return 1 if base.verbose: print "hydrogen angle(s) too sharp (< %g)" % minHydAngle return 0
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 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 metalClash(metalPos, pos, parentPos): if metalPos.distance(parentPos) > _metalDist: return False if chimera.angle(parentPos, pos, metalPos) > 135.0: return True return False
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