def correct_o_position(res, psi): n, ca, c, o = [res.find_atom(name) for name in ['N', 'CA', 'C', 'O']] from chimerax.geometry import rotation, dihedral d = dihedral(*[a.coord for a in (n, ca, c, o)]) r = rotation(c.coord - ca.coord, psi + 180 - d, center=c.coord) from chimerax.atomic import Atoms Atoms([o]).transform(r)
def log_torsion_command(bond_rotator): bond = bond_rotator.rotation.bond ms_atom = bond_rotator.moving_side fs_atom = bond.other_atom(ms_atom) ms_atom2 = _connected_atom(ms_atom, fs_atom) fs_atom2 = _connected_atom(fs_atom, ms_atom) if ms_atom2 is None or fs_atom2 is None: return # No connected atom to define a torsion side = '' if bond.smaller_side is ms_atom else 'move large' from chimerax.geometry import dihedral torsion = dihedral(fs_atom2.scene_coord, fs_atom.scene_coord, ms_atom.scene_coord, ms_atom2.scene_coord) res = ms_atom.residue if ms_atom2.residue is res and fs_atom.residue is res and fs_atom2.residue is res: # Use simpler atom spec for the common case of rotating a side chain. atom_specs = '%s@%s,%s,%s,%s' % (res.string(style='command'), ms_atom2.name, ms_atom.name, fs_atom.name, fs_atom2.name) else: atom_specs = '%s %s %s %s' % ( fs_atom2.string(style='command'), fs_atom.string(style='command'), ms_atom.string(style='command'), ms_atom2.string(style='command')) cmd = 'torsion %s %.2f %s' % (atom_specs, torsion, side) ses = ms_atom.structure.session from chimerax.core.commands import run run(ses, cmd)
def cmd_torsion(session, atoms, value=None, *, move="small"): """Wrapper called by command line.""" if len(atoms) != 4: raise UserError( "Must specify exactly 4 atoms for 'torsion' command; you specified %d" % len(atoms)) a1, a2, a3, a4 = atoms from chimerax.geometry import dihedral cur_torsion = dihedral(*[a.scene_coord for a in atoms]) if value is None: session.logger.info( "Torsion angle for atoms %s %s %s %s is %g\N{DEGREE SIGN}" % (a1, a2.string(relative_to=a1), a3.string(relative_to=a2), a4.string(relative_to=a3), cur_torsion)) return for nb, bond in zip(a2.neighbors, a2.bonds): if nb == a3: break else: raise UserError( "To set torsion, middle two atoms (%s %s) must be bonded;they aren't" % (a2, a3.string(relative_to=a2))) move_smaller = move == "small" mgr = session.bond_rotations from .manager import BondRotationError try: rotater = mgr.new_rotation(bond, move_smaller_side=move_smaller) except BondRotationError as e: raise UserError(str(e)) rotater.angle += value - cur_torsion mgr.delete_rotation(rotater)
def build_next_atom_from_geometry(residue, residue_anchor, template_anchor, template_new_atom): from chimerax.atomic import struct_edit from chimerax.geometry import distance, angle, dihedral r = residue m = r.structure tnext = template_new_atom if tnext is None: raise TypeError('Template does not contain an atom with that name!') tstub = template_anchor rstub = residue_anchor existing_rstub_neighbors = rstub.neighbors n1 = rstub n2 = n3 = None t_direct_neighbors = [] r_direct_neighbors = [] for a2 in tstub.neighbors: if a2.element.name != 'H': n2 = r.find_atom(a2.name) if n2: t_direct_neighbors.append(a2) r_direct_neighbors.append(n2) if len(t_direct_neighbors) > 1: a2, a3 = t_direct_neighbors[:2] n2, n3 = r_direct_neighbors[:2] else: a2 = t_direct_neighbors[0] n2 = r_direct_neighbors[0] if not n2: raise TypeError( 'No n2 found - Not enough connected atoms to form a dihedral!') if not n3: for a3 in a2.neighbors: if a3 not in (a2, tstub) and a3.element.name != 'H': n3 = r.find_atom(a3.name) if n3: break if not n3: raise TypeError( 'No n3 found - Not enough connected atoms to form a dihedral!') # print('Building next atom {} from geometry of {}'.format(template_new_atom.name, # ','.join([n.name for n in (n1, n2, n3)]))) dist = distance(tnext.coord, tstub.coord) ang = angle(tnext.coord, tstub.coord, a2.coord) dihe = dihedral(tnext.coord, tstub.coord, a2.coord, a3.coord) # print('{}: {} {} {}'.format(next_atom_name, dist, ang, dihe)) a = struct_edit.add_dihedral_atom(tnext.name, tnext.element, n1, n2, n3, dist, ang, dihe) a.occupancy = rstub.occupancy a.bfactor = rstub.bfactor return a
def interpolate_dihedral(i0, i1, i2, i3, coords0, coords1, f, coord_set): """ Computer coordinate of atom a0 by interpolating dihedral angle defined by atoms (a0, a1, a2, a3). """ t0 = time() from chimerax.geometry import distance, angle, dihedral, dihedral_point c00 = coords0[i0] c01 = coords0[i1] c02 = coords0[i2] c03 = coords0[i3] length0 = distance(c00, c01) angle0 = angle(c00, c01, c02) dihed0 = dihedral(c00, c01, c02, c03) c10 = coords1[i0] c11 = coords1[i1] c12 = coords1[i2] c13 = coords1[i3] length1 = distance(c10, c11) angle1 = angle(c10, c11, c12) dihed1 = 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 = coord_set[i1, :] c2 = coord_set[i2, :] c3 = coord_set[i3, :] t2 = time() c0 = dihedral_point(c1, c2, c3, length, angle, dihed) t3 = time() coord_set[i0:] = c0 t1 = time() global iit, dpt iit += t1 - t0 dpt += t3 - t2
def flip_if_necessary(residue, chi_atom_names): if residue.is_missing_heavy_template_atoms(): return chis = [[*chi_atom_names[:3], chi_atom_names[3][i]] for i in range(2)] dihedrals = [] from chimerax.geometry import dihedral for chi_atom_names in chis: atoms = [residue.find_atom(name) for name in chi_atom_names] dihedrals.append(dihedral(*[a.coord for a in atoms])) if abs(dihedrals[1]) < abs(dihedrals[0]): if swap_equivalent_atoms(residue): correct_dihedral_restraint(residue) correct_position_and_distance_restraints(residue) return True return False
def form_dihedral(res_bud, real1, tmpl_res, a, b, pos=None, dihed=None): from chimerax.atomic.struct_edit import add_atom, add_dihedral_atom res = res_bud.residue if pos: return add_atom(a.name, a.element, res, pos, info_from=real1) # use neighbors of res_bud rather than real1 to avoid clashes with # other res_bud neighbors in case bond to real1 neighbor freely rotates inres = [ nb for nb in res_bud.neighbors if nb != real1 and nb.residue == res ] if len(inres) < 1: inres = [x for x in res.atoms if x not in [res_bud, real1]] if real1.residue != res or len(inres) < 1: raise AssertionError( "Can't form in-residue dihedral for %s of residue %s" % (res_bud, res)) if dihed: real1 = res.find_atom("C1'") real2 = res.find_atom("O4'") else: real2 = inres[0] xyz0, xyz1, xyz2 = [ tmpl_res.find_atom(a.name).coord for a in (res_bud, real1, real2) ] xyz = a.coord blen = b.length from chimerax.geometry import angle, dihedral ang = angle(xyz, xyz0, xyz1) if dihed is None: dihed = dihedral(xyz, xyz0, xyz1, xyz2) return add_dihedral_atom(a.name, a.element, res_bud, real1, real2, blen, ang, dihed, info_from=real1)
def post_add(session, fake_n, fake_c): # fix up non-"true" terminal residues (terminal simply because # next residue is missing) for fn in fake_n: n = fn.find_atom("N") ca = fn.find_atom("CA") c = fn.find_atom("C") if not n or not ca or not c: continue dihed = None for cnb in c.neighbors: if cnb.name == "N": pn = cnb break else: dihed = 0.0 if dihed is None: pr = pn.residue pc = pr.find_atom("C") pca = pr.find_atom("CA") if pr.name == "PRO": ph = pr.find_atom("CD") else: ph = pr.find_atom("H") if not pc or not pca or not ph: dihed = 0.0 add_nh = True for nb in n.neighbors: if nb.element.number == 1: if nb.name == "H": add_nh = False if fn.name == "PRO": nb.structure.delete_atom(nb) else: nb.structure.delete_atom(nb) if fn.name == "PRO": n.idatm_type = "Npl" continue if add_nh: if dihed is None: from chimerax.geometry import dihedral dihed = dihedral(pc.coord, pca.coord, pn.coord, ph.coord) session.logger.info("Adding 'H' to %s" % str(fn)) from chimerax.atomic.struct_edit import add_dihedral_atom h = add_dihedral_atom("H", "H", n, ca, c, 1.01, 120.0, dihed, bonded=True) h.color = determine_h_color(n) # also need to set N's IDATM type, because if we leave it as # N3+ then the residue will be identified by AddCharge as # terminal and there will be no charge for the H atom n.idatm_type = "Npl" for fc in fake_c: c = fc.find_atom("C") if not c: continue for nb in c.neighbors: if nb.element.number == 1: session.logger.info( "%s is not terminus, removing H atom from 'C'" % str(fc)) nb.structure.delete_atom(nb) # the N proton may have been named 'HN'; fix that hn = fc.find_atom("HN") if not hn: continue n = hn.neighbors[0] h = add_atom("H", "H", fc, hn.coord, serial_number=hn.serial_number, bonded_to=n) h.color = determine_h_color(n) h.hide = n.hide fc.structure.delete_atom(hn)
def template_swap_res(res, res_type, *, preserve=False, bfactor=None): """change 'res' into type 'res_type'""" fixed, buds, start, end = get_res_info(res) if res_type == "HIS": res_type = "HIP" if res_type in ["A", "C", "G", "T" ] and res.name in ["DA", "DC", "DT", "DG"]: res_type = "D" + res_type from chimerax.atomic import TmplResidue, Atom tmpl_res = TmplResidue.get_template(res_type, start=start, end=end) if not tmpl_res: raise TemplateError("No connectivity template for residue '%s'" % res_type) # sanity check: does the template have the bud atoms? for bud in buds: if tmpl_res.find_atom(bud) is None: raise TemplateError("New residue type (%s) not compatible with" " starting residue type (%s)" % (res_type, res.name)) color_by_element = False uniform_color = res.find_atom(buds[0]).color het = res.find_atom("N") or res.find_atom("O4'") if het: carbon = res.find_atom("CA") or res.find_atom("C4'") if carbon: color_by_element = het.color != carbon.color if color_by_element: carbon_color = carbon.color else: uniform_color = het.color bfactor = bfactor_for_res(res, bfactor) if preserve: if "CA" in fixed and res_type not in ['GLY', 'ALA']: raise TemplateSwapError( "'preserve' keyword not yet implemented for amino acids") a1 = res.find_atom("O4'") a2 = res.find_atom("C1'") if not a1 or not a2: preserve_pos = None else: dihed_names = {"N9": ["C4", "C8"], "N1": ["C2", "C6"]} a3 = res.find_atom("N9") or res.find_atom("N1") if a3: if a2 not in a3.neighbors: preserve_pos = None else: preserve_pos = a3.coord else: preserve_pos = None if preserve_pos: p1, p2, p3 = [a.coord for a in (a1, a2, a3)] preserved_pos = False prev_name, alt_name = dihed_names[a3.name] a4 = res.find_atom(prev_name) if a4 and a3 in a4.neighbors: p4 = a4.coord from chimerax.geometry import dihedral preserve_dihed = dihedral(p1, p2, p3, p4) else: preserve_dihed = None else: preserve_dihed = None # prune non-backbone atoms for a in res.atoms: if a.name not in fixed: a.structure.delete_atom(a) # add new sidechain new_atoms = [] xf = None from chimerax.atomic.struct_edit import add_bond while len(buds) > 0: bud = buds.pop() tmpl_bud = tmpl_res.find_atom(bud) res_bud = res.find_atom(bud) try: info = Atom.idatm_info_map[tmpl_bud.idatm_type] geom = info.geometry subs = info.substituents except KeyError: raise AssertionError( "Can't determine atom type information for atom %s of residue %s" % (bud, res)) # use .coord rather than .scene_coord: we want to set the new atom's coord, # to which the proper xform will then be applied for a, b in zip(tmpl_bud.neighbors, tmpl_bud.bonds): if a.element.number == 1: # don't add hydrogens continue if res.find_atom(a.name): res_bonder = res.find_atom(a.name) if res_bonder not in res_bud.neighbors: add_bond(a, res_bonder) continue new_atom = None num_bonded = len(res_bud.bonds) if num_bonded >= subs: raise AssertionError( "Too many atoms bonded to %s of residue %s" % (bud, res)) if num_bonded == 0: raise AssertionError( "Atom %s of residue %s has no neighbors after pruning?!?" % (bud, res)) # since fused ring systems may have distorted bond angles, always use dihedral placement real1 = res_bud.neighbors[0] kw = {} if preserve: if preserve_pos and not preserved_pos: kw['pos'] = preserve_pos preserved_pos = True preserved_name = a.name elif preserve_dihed is not None: prev_name, alt_name = dihed_names[preserved_name] if a.name == prev_name: kw['dihed'] = preserve_dihed elif a.name == alt_name: kw['dihed'] = preserve_dihed + 180.0 if not kw and xf is not None: kw['pos'] = xf * a.coord new_atom = form_dihedral(res_bud, real1, tmpl_res, a, b, **kw) new_atom.draw_mode = res_bud.draw_mode new_atom.bfactor = bfactor if color_by_element: if new_atom.element.name == "C": new_atom.color = carbon_color else: from chimerax.atomic.colors import element_color new_atom.color = element_color(new_atom.element.number) else: new_atom.color = uniform_color new_atoms.append(new_atom) for bonded in a.neighbors: bond_atom = res.find_atom(bonded.name) if not bond_atom: continue add_bond(new_atom, bond_atom) buds.append(new_atom.name) # once we've placed 3 side chain atoms, we use superpositioning to # place the remainder of the side chain, since dihedrals will # likely distort ring closures if 'preserve' is true if buds and not xf and len(new_atoms) >= 3: placed_positions = [] tmpl_positions = [] for na in new_atoms: placed_positions.append(na.coord) tmpl_positions.append(tmpl_res.find_atom(na.name).coord) import numpy from chimerax.geometry import align_points xf = align_points(numpy.array(tmpl_positions), numpy.array(placed_positions))[0] res.name = res_type