def add_disulfides_from_model_metadata(model): m_id = model.id_string from chimerax.core.commands import atomspec metadata = model.metadata try: disulfide_list = metadata['SSBOND'] except KeyError: return from chimerax.atomic.struct_edit import add_bond for disulfide in disulfide_list: sym1 = None sym2 = None d = disulfide.split() chain1, res1 = d[3], d[4] chain2, res2 = d[6], d[7] if len(d) > 8: sym1, sym2 = d[8], d[9] if sym1 is not None and sym1 != sym2: # Disulfide across a symmetry interface. Ignore for now. continue arg = atomspec.AtomSpecArg('ss') thearg = '#{}/{}:{}@{}|#{}/{}:{}@{}'.format(m_id, chain1, res1, 'SG', m_id, chain2, res2, 'SG') aspec = arg.parse(thearg, model.session) atoms = aspec[0].evaluate(model.session).atoms bonds = atoms.intra_bonds if not len(bonds): a1, a2 = atoms add_bond(a1, a2)
def add_bonds_from_template_by_matched_names(residue, template): from chimerax.atomic.struct_edit import add_bond amap = {} for a in residue.atoms: ta = template.find_atom(a.name) if ta is not None: amap[a] = ta for a, ta in amap.items(): for n in ta.neighbors: rn = residue.find_atom(n.name) if rn is not None and rn not in a.neighbors: add_bond(a, rn)
def run_script(session): from chimerax.atomic import selected_atoms from chimerax.core.errors import UserError sel = selected_atoms(session) if len(sel) != 2: raise UserError('Must have exactly two atoms selected!') us = sel.unique_structures if len(us) != 1: raise UserError('Both atoms must be from the same structure!') m = us[0] from chimerax.atomic.struct_edit import add_bond add_bond(*sel)
def add_metal_bonds_from_template(residue, template): m = residue.structure metal_atoms = [a for a in template.atoms if a.element.is_metal] from chimerax.atomic.struct_edit import add_bond for met in metal_atoms: rmet = residue.find_atom(met.name) if rmet is None: raise TypeError( 'Residue does not have the required metal ion! Use fix_residue_from_template() instead.' ) for n in met.neighbors: rn = residue.find_atom(n.name) if not rn in rmet.neighbors: add_bond(rmet, rn)
def create_disulfide(cys1, cys2): from chimerax.core.errors import UserError if cys1.structure != cys2.structure: raise UserError('Both cysteine residues must be in the same model!') structure = cys1.structure s1 = cys1.find_atom('SG') s2 = cys2.find_atom('SG') if s1 is None or s2 is None: raise UserError( 'Missing SG atom! Are both residues complete cysteines?') if s2 in s1.neighbors: raise UserError('These residues are already disulfide bonded!') for s in (s1, s2): for a in s.neighbors: if a.element.name == 'H': a.delete() from chimerax.atomic.struct_edit import add_bond add_bond(s1, s2)
def new_residue_from_template(model, template, chain_id, center, residue_number=None, insert_code=' ', b_factor=50, precedes=None): ''' Create a new residue based on a template, and add it to the model. ''' if residue_number is None: if chain_id in model.residues.chain_ids: residue_number = suggest_new_residue_number_for_ligand( model, chain_id) else: residue_number = 0 import numpy from chimerax.atomic import Atom t_coords = numpy.array([a.coord for a in template.atoms]) t_center = t_coords.mean(axis=0) t_coords += numpy.array(center) - t_center tatom_to_atom = {} r = model.new_residue(template.name, chain_id, residue_number, insert=insert_code, precedes=precedes) from chimerax.atomic.struct_edit import add_bond, add_atom for i, ta in enumerate(template.atoms): a = tatom_to_atom[ta] = add_atom(ta.name, ta.element, r, t_coords[i], bfactor=b_factor) for tn in ta.neighbors: n = tatom_to_atom.get(tn, None) if n is not None: add_bond(a, n) return r
def build_next_atom_from_coords(residue, found_neighbors, template_new_atom): # print('Building next atom {} from aligned coords of {}'.format(template_new_atom.name, # ','.join([f[0].name for f in found_neighbors]))) bonded_to = [f[0] for f in found_neighbors] m = residue.structure n = len(found_neighbors) found = set(f[0] for f in found_neighbors) while len(found_neighbors) < 3: for ra, ta in found_neighbors: for tn in ta.neighbors: rn = residue.find_atom(tn.name) if rn and rn not in found: found_neighbors.append((rn, tn)) found.add(rn) if len(found_neighbors) <= n: residue.session.logger.warning( "Couldn't find more than two neighbor atoms. Falling back to simple geometry-based addition." ) return build_next_atom_from_geometry(residue, *found_neighbors[0], template_new_atom) n = len(found_neighbors) from chimerax.geometry import align_points import numpy ra_coords = numpy.array([f[0].coord for f in found_neighbors]) ta_coords = numpy.array([f[1].coord for f in found_neighbors]) bfactor = numpy.mean([f[0].bfactor for f in found_neighbors]) occupancy = numpy.mean([f[0].occupancy for f in found_neighbors]) tf, rms = align_points(ta_coords, ra_coords) from chimerax.atomic.struct_edit import add_atom ta = template_new_atom a = add_atom(ta.name, ta.element, residue, tf * ta.coord, occupancy=occupancy, bfactor=bfactor) from chimerax.atomic.struct_edit import add_bond for b in bonded_to: add_bond(a, b)
def use_rotamer(session, res, rots, retain=False, log=False, bfactor=None): """Takes a Residue instance and either a list or dictionary of rotamers (as returned by get_rotamers, i.e. with backbone already matched) and swaps the Residue's side chain with the given rotamers. If the rotamers are a dictionary, then the keys should match the alt locs of the CA atom, and the corresponding rotamer will be used for that alt loc. If the alt locs are a list, if the list has only one rotamer then that rotamer will be used for each CA alt loc. If the list has multiple rotamers, then the CA must have only one alt loc (namely ' ') and all the rotamers will be attached, using different alt loc characters for each. If 'retain' is True, existing side chains will be retained. If 'bfactor' is None, then the current highest existing bfactor in the residue will be used. """ N = res.find_atom("N") CA = res.find_atom("CA") C = res.find_atom("C") if not N or not C or not CA: raise LimitationError( "N, CA, or C missing from %s: needed for side-chain pruning algorithm" % res) import string alt_locs = string.ascii_uppercase + string.ascii_lowercase + string.digits + string.punctuation if retain and CA.alt_locs: raise LimitationError( "Cannot retain side chains if multiple CA alt locs") ca_alt_locs = [' '] if not CA.alt_locs else CA.alt_locs if not isinstance(rots, dict): # reformat as dictionary if CA.alt_locs and len(rots) > 1: raise LimitationError( "Cannot add multiple rotamers to multi-position backbone") retained_alt_locs = side_chain_locs(res) if retain else [] num_retained = len(retained_alt_locs) if len(rots) + num_retained > len(alt_locs): raise LimitationError("Don't have enough unique alternate " "location characters to place %d rotamers." % len(rots)) if len(rots) + num_retained > 1: rots = { loc: rot for loc, rot in zip( [c for c in alt_locs if c not in retained_alt_locs][:len(rots)], rots) } else: rots = {alt_loc: rots[0] for alt_loc in ca_alt_locs} swap_type = list(rots.values())[0].residues[0].name if retain and res.name != swap_type: raise LimitationError( "Cannot retain side chains if rotamers are a different residue type" ) rot_anchors = {} for rot in rots.values(): rot_res = rot.residues[0] rot_N, rot_CA = rot_res.find_atom("N"), rot_res.find_atom("CA") if not rot_N or not rot_CA: raise LimitationError( "N or CA missing from rotamer: cannot matchup with original residue" ) rot_anchors[rot] = (rot_N, rot_CA) color_by_element = N.color != CA.color if color_by_element: carbon_color = CA.color else: uniform_color = N.color # prune old side chain bfactor = bfactor_for_res(res, bfactor) if not retain: res_atoms = res.atoms side_atoms = res_atoms.filter(res_atoms.is_side_onlys) serials = {a.name: a.serial_number for a in side_atoms} side_atoms.delete() else: serials = {} # for proline, also prune amide hydrogens if swap_type == "PRO": for nnb in N.neighbors[:]: if nnb.element.number == 1: N.structure.delete_atom(nnb) tot_prob = sum([r.rotamer_prob for r in rots.values()]) with CA.suppress_alt_loc_change_notifications(): res.name = swap_type from chimerax.atomic.struct_edit import add_atom, add_bond for alt_loc, rot in rots.items(): if CA.alt_locs: CA.alt_loc = alt_loc if log: extra = " using alt loc %s" % alt_loc if alt_loc != ' ' else "" session.logger.info( "Applying %s rotamer (chi angles: %s) to %s%s" % (rot_res.name, " ".join(["%.1f" % c for c in rot.chis]), res, extra)) # add new side chain rot_N, rot_CA = rot_anchors[rot] visited = set([N, CA, C]) sprouts = [rot_CA] while sprouts: sprout = sprouts.pop() built_sprout = res.find_atom(sprout.name) for nb in sprout.neighbors: built_nb = res.find_atom(nb.name) if tot_prob == 0.0: # some rotamers in Dunbrack are zero prob! occupancy = 1.0 / len(rots) else: occupancy = rot.rotamer_prob / tot_prob if not built_nb: serial = serials.get(nb.name, None) built_nb = add_atom(nb.name, nb.element, res, nb.coord, serial_number=serial, bonded_to=built_sprout, alt_loc=alt_loc) built_nb.occupancy = occupancy built_nb.bfactor = bfactor if color_by_element: if built_nb.element.name == "C": built_nb.color = carbon_color else: from chimerax.atomic.colors import element_color built_nb.color = element_color( built_nb.element.number) else: built_nb.color = uniform_color elif built_nb not in visited: built_nb.set_alt_loc(alt_loc, True) built_nb.coord = nb.coord built_nb.occupancy = occupancy built_nb.bfactor = bfactor if built_nb not in visited: sprouts.append(nb) visited.add(built_nb) if built_nb not in built_sprout.neighbors: add_bond(built_sprout, built_nb)
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
def get_rotamers(session, res, phi=None, psi=None, cis=False, res_type=None, rot_lib="Dunbrack", log=False): """Takes a Residue instance and optionally phi/psi angles (if different from the Residue), residue type (e.g. "TYR"), and/or rotamer library name. Returns a list of AtomicStructure instances (sublass of AtomicStructure). The AtomicStructure are each a single residue (a rotamer) and are in descending probability order. Each has an attribute "rotamer_prob" for the probability and "chis" for the chi angles. """ res_type = res_type or res.name if res_type == "ALA" or res_type == "GLY": raise NoResidueRotamersError("No rotamers for %s" % res_type) if not isinstance(rot_lib, RotamerLibrary): rot_lib = session.rotamers.library(rot_lib) # check that the residue has the n/c/ca atoms needed to position the rotamer # and to ensure that it is an amino acid from chimerax.atomic import Residue match_atoms = {} for bb_name in Residue.aa_min_backbone_names: match_atoms[bb_name] = a = res.find_atom(bb_name) if a is None: raise LimitationError("%s missing from %s; needed to position CB" % (bb_name, res)) match_atoms["CB"] = res.find_atom("CB") if not phi and not psi: phi, psi = res.phi, res.psi omega = res.omega cis = False if omega is None or abs(omega) > 90 else True if log: def _info(ang): if ang is None: return "none" return "%.1f" % ang if match_atoms["CA"].alt_locs: al_info = " (alt loc %s)" % match_atoms["CA"].alt_loc else: al_info = "" session.logger.info("%s%s: phi %s, psi %s %s" % (res, al_info, _info(phi), _info(psi), "cis" if cis else "trans")) session.logger.status("Retrieving rotamers from %s library" % rot_lib.display_name) res_template_func = rot_lib.res_template_func params = rot_lib.rotamer_params(res_type, phi, psi, cis=cis) session.logger.status("Rotamers retrieved from %s library" % rot_lib.display_name) mapped_res_type = rot_lib.res_name_mapping.get(res_type, res_type) template = rot_lib.res_template_func(mapped_res_type) tmpl_N = template.find_atom("N") tmpl_CA = template.find_atom("CA") tmpl_C = template.find_atom("C") tmpl_CB = template.find_atom("CB") if match_atoms['CB']: res_match_atoms, tmpl_match_atoms = [ match_atoms[x] for x in ("C", "CA", "CB") ], [tmpl_C, tmpl_CA, tmpl_CB] else: res_match_atoms, tmpl_match_atoms = [ match_atoms[x] for x in ("N", "CA", "C") ], [tmpl_N, tmpl_CA, tmpl_C] from chimerax.geometry import align_points from numpy import array xform, rmsd = align_points(array([fa.coord for fa in tmpl_match_atoms]), array([ta.coord for ta in res_match_atoms])) n_coord = xform * tmpl_N.coord ca_coord = xform * tmpl_CA.coord cb_coord = xform * tmpl_CB.coord info = Residue.chi_info[mapped_res_type] bond_cache = {} angle_cache = {} from chimerax.atomic.struct_edit import add_atom, add_dihedral_atom, add_bond structs = [] middles = {} ends = {} for i, rp in enumerate(params): s = AtomicStructure(session, name="rotamer %d" % (i + 1)) structs.append(s) r = s.new_residue(mapped_res_type, 'A', 1) registerer = "swap_res get_rotamers" AtomicStructure.register_attr(session, "rotamer_prob", registerer, attr_type=float) s.rotamer_prob = rp.p AtomicStructure.register_attr(session, "chis", registerer) s.chis = rp.chis rot_N = add_atom("N", tmpl_N.element, r, n_coord) rot_CA = add_atom("CA", tmpl_CA.element, r, ca_coord, bonded_to=rot_N) rot_CB = add_atom("CB", tmpl_CB.element, r, cb_coord, bonded_to=rot_CA) todo = [] for j, chi in enumerate(rp.chis): n3, n2, n1, new = info[j] b_len, angle = _len_angle(new, n1, n2, template, bond_cache, angle_cache) n3 = r.find_atom(n3) n2 = r.find_atom(n2) n1 = r.find_atom(n1) new = template.find_atom(new) a = add_dihedral_atom(new.name, new.element, n1, n2, n3, b_len, angle, chi, bonded=True) todo.append(a) middles[n1] = [a, n1, n2] ends[a] = [a, n1, n2] # if there are any heavy non-backbone atoms bonded to template # N and they haven't been added by the above (which is the # case for Richardson proline parameters) place them now for tnnb in tmpl_N.neighbors: if r.find_atom(tnnb.name) or tnnb.element.number == 1: continue tnnb_coord = xform * tnnb.coord add_atom(tnnb.name, tnnb.element, r, tnnb_coord, bonded_to=rot_N) # fill out bonds and remaining heavy atoms from chimerax.geometry import distance, align_points done = set([rot_N, rot_CA]) while todo: a = todo.pop(0) if a in done: continue tmpl_A = template.find_atom(a.name) for bonded, bond in zip(tmpl_A.neighbors, tmpl_A.bonds): if bonded.element.number == 1: continue rbonded = r.find_atom(bonded.name) if rbonded is None: # use middles if possible... try: p1, p2, p3 = middles[a] conn = p3 except KeyError: p1, p2, p3 = ends[a] conn = p2 t1 = template.find_atom(p1.name) t2 = template.find_atom(p2.name) t3 = template.find_atom(p3.name) xform = align_points( array([t.coord for t in [t1, t2, t3]]), array([p.coord for p in [p1, p2, p3]]))[0] pos = xform * template.find_atom(bonded.name).coord rbonded = add_atom(bonded.name, bonded.element, r, pos, bonded_to=a) middles[a] = [rbonded, a, conn] ends[rbonded] = [rbonded, a, conn] if a not in rbonded.neighbors: add_bond(a, rbonded) if rbonded not in done: todo.append(rbonded) done.add(a) return structs
def add_amino_acid_residue(model, resname, prev_res=None, next_res=None, chain_id=None, number=None, center=None, insertion_code=' ', add_b_factor=0, occupancy=1, phi=-135, psi=135): session = model.session if (not chain_id or not number or center is None) and (not prev_res and not next_res): raise TypeError('If no anchor residues are specified, chain ID, ' 'number and center must be provided!') if prev_res and next_res: raise TypeError('Cannot specify both previous and next residues!') other_atom = None insertion_point = None import numpy if prev_res: ref_res = prev_res b_factor = prev_res.atoms[numpy.in1d( prev_res.atoms.names, ('N', 'CA', 'C', 'O', 'CB'))].bfactors.mean() + add_b_factor pri = model.residues.index(prev_res) if pri > 0 and pri < len(model.residues) - 1: insertion_point = model.residues[pri + 1] catom = prev_res.find_atom('C') for n in catom.neighbors: if n.residue != prev_res: raise TypeError( 'This residue already has another bonded to its ' 'C terminus!') if chain_id is not None: session.logger.warning( 'AddAA: chain_id argument is ignored when adding to an existing residue.' ) chain_id = prev_res.chain_id oxt = prev_res.find_atom('OXT') if oxt is not None: oxt.delete() elif next_res: ref_res = next_res b_factor = next_res.atoms[numpy.in1d( next_res.atoms.names, ('N', 'CA', 'C', 'O', 'CB'))].bfactors.mean() + add_b_factor insertion_point = next_res natom = next_res.find_atom('N') for n in natom.neighbors: if n.residue != next_res: raise TypeError( 'This residue already has another bonded to its ' 'N terminus!') if chain_id is not None: session.logger.warning( 'AddAA: chain_id argument is ignored when adding to an existing residue.' ) chain_id = next_res.chain_id for hname in ('H2', 'H3'): h = next_res.find_atom(hname) if h is not None: h.delete() h = next_res.find_atom('H1') if h is not None: h.name = 'H' if next_res.name == 'PRO': h = next_res.find_atom('H') if h: h.delete() if number is None: if prev_res: number = prev_res.number + 1 elif next_res: number = next_res.number - 1 from chimerax import mmcif tmpl = mmcif.find_template_residue(session, resname) from .place_ligand import new_residue_from_template import numpy # delete extraneous atoms r = new_residue_from_template(model, tmpl, chain_id, [0, 0, 0], number, insert_code=insertion_code, b_factor=b_factor, precedes=insertion_point) r.atoms[numpy.in1d(r.atoms.names, ['OXT', 'HXT', 'H2', 'H1', 'HN1', 'HN2'])].delete() # Translate and rotate residue to (roughly) match the desired position if not next_res and not prev_res: r.atoms.coords += numpy.array(center) - r.atoms.coords.mean(axis=0) b_factor = max(add_b_factor, 5) else: from chimerax.geometry import align_points from chimerax.atomic.struct_edit import add_bond if prev_res: correct_o_position(prev_res, psi) add_bond(r.find_atom('N'), prev_res.find_atom('C')) n_pos = _find_next_N_position(prev_res) ca_pos = _find_next_CA_position(n_pos, prev_res) c_pos = _find_next_C_position(ca_pos, n_pos, prev_res, phi) target_coords = numpy.array([n_pos, ca_pos, c_pos]) align_coords = numpy.array( [r.find_atom(a).coord for a in ['N', 'CA', 'C']]) elif next_res: add_bond(r.find_atom('C'), next_res.find_atom('N')) c_pos = _find_prev_C_position(next_res, psi) ca_pos = _find_prev_CA_position(c_pos, next_res) #o_pos = _find_prev_O_position(c_pos, next_res) n_pos = _find_prev_N_position(c_pos, ca_pos, next_res, psi) target_coords = numpy.array([c_pos, ca_pos, n_pos]) align_coords = numpy.array( [r.find_atom(a).coord for a in ['C', 'CA', 'N']]) tf = align_points(align_coords, target_coords)[0] r.atoms.coords = tf * r.atoms.coords correct_o_position(r, psi) if r.name in ('GLU', 'ASP'): fix_amino_acid_protonation_state(r) if r.name == 'PRO': r.atoms[r.atoms.names == 'H'].delete() r.atoms.bfactors = b_factor r.atoms.occupancies = occupancy from . import copy_atom_style_from copy_atom_style_from(session, r.atoms, ref_res) model.atoms.selecteds = False r.atoms.selecteds = True return r
def fix_residue_from_template(residue, template, rename_atoms_only=False, rename_residue=False, match_by='name', template_indices=None): import numpy from chimerax.atomic import Atoms from chimerax.atomic.struct_edit import add_bond if any([numpy.any(numpy.isnan(a.coord)) for a in template.atoms]): raise TypeError('Template is missing one or more atom coordinates!') matched_nodes, residue_extra, template_extra = find_maximal_isomorphous_fragment( residue, template, limit_template_indices=template_indices, match_by=match_by) if len(matched_nodes) < 3: from chimerax.atomic import Residue if residue.polymer_type == Residue.PT_NONE: final_string = ( f'Try deleting this residue and replacing it with "isolde add ligand {residue.name} - or have you just forgotten ' f'to add hydrogens?') elif residue.polymer_type == Residue.PT_AMINO: final_string = ( f'Try deleting this residue and replacing it with "isolde add aa {residue.name}' ) else: final_string = ( 'Adding of nucleic acid residues is not currently possible in ISOLDE. To move forward, just delete this residue.' ) raise UserError( f'Residue {residue.name} {residue.chain_id}{residue.number}{residue.insertion_code} ' f'has only {len(matched_nodes)} atoms in common with the template. At least 3 are needed to rebuild automatically. ' f'{final_string}') m = residue.structure session = m.session rnames = set(residue.atoms.names) tnames = set([a.name for a in template.atoms]) # Delete any isolated atoms and rebuild from template if len(residue_extra): if rename_atoms_only: session.logger.warning( 'The following atoms in {} {}{} did not match template {}, and will not be renamed: {}.' .format(residue.name, residue.chain_id, residue.number, template.name, ', '.join(residue_extra.names))) else: session.logger.info( 'Deleted the following atoms from residue {} {}{}{}: {}'. format(residue.name, residue.chain_id, residue.number, residue.insertion_code, ', '.join(residue_extra.names))) residue_extra.delete() conn_ratoms = Atoms(matched_nodes.keys()) renamed_atoms = [] for ratom, tatom in matched_nodes.items(): if ratom.name != tatom.name: renamed_atoms.append((ratom.name, tatom.name)) ratom.name = tatom.name if len(renamed_atoms): warn_str = '{} atoms were automatically renamed to match the template: '.format( len(renamed_atoms)) warn_str += ', '.join(['->'.join(apair) for apair in renamed_atoms]) session.logger.warning(warn_str) if rename_atoms_only: return still_missing = set(template_extra) remaining = len(still_missing) found = set() while remaining: for ta in still_missing: found_neighbors = [] for tn in ta.neighbors: ra = residue.find_atom(tn.name) if ra: found_neighbors.append((ra, tn)) if len(found_neighbors) == 0: continue else: ra, tn = found_neighbors[0] build_next_atom_from_geometry(residue, ra, tn, ta) found.add(ta) still_missing = still_missing.difference(found) if len(still_missing) and not len(found): raise RuntimeError( 'Failed to add atoms on last iteration for residue {}. Still missing: {}' .format('{}{}'.format(residue.chain_id, residue.number), ','.join(still_missing))) # m.session.logger.status('Still missing: {}'.format( # ','.join([ta.name for ta in still_missing]) # )) remaining = len(still_missing) bonds = set() for a in template.atoms: bonds.update(set(a.bonds)) for b in bonds: a1, a2 = [residue.find_atom(a.name) for a in b.atoms] if a1 is None or a2 is None: continue if a2 not in a1.neighbors: add_bond(a1, a2) if rename_residue: residue.name = template.name
def merge_fragment(target_model, residues, chain_id=None, renumber_from=None, anchor_n=None, anchor_c=None, transform=None): ''' Copy the atoms from a fragment into the current model, optionally reassigning chain ID and numbers. If alternate conformers are present, only the active one will be copied. * Args: - target_model: the model to copy into - residues: the residues to be copied. Isotropic B-factors will be copied, but aniso_u records will be ignored. - chain_id: if provided, all residues must be from one chain. The copied residues will be given this chain ID. - renumber_from: if provided, all residues will be renumbered with an offset of (lowest residue number - renumber_from) - anchor_n: an amino acid residue or None. If provided, the first amino acid residue in the fragment will be linked to this one. Throws an error if anchor_n has another residue linked at its C-terminus. - anchor_c: an amino acid residue or None. If provided, the last amino acid residue in the fragment will be linked to this one. Throws an error if anchor_c has another residue linked at its C-terminus. - transform: a Place or None. If provided, the atoms will be placed at the transformed coordinates. ''' from chimerax.core.errors import UserError us = residues.unique_structures if len(us) != 1: raise UserError( 'All residues to be copied must be from the same model!') if (chain_id or anchor_n or anchor_c or (renumber_from is not None)) \ and len(residues.unique_chain_ids) != 1: raise UserError( 'If reassigning chain ID, renumbering or specifying ' 'N- and/or C-terminal anchors, all residues to be copied must be ' 'from a single chain!') from chimerax.atomic import Residue, Residues, Atoms if (anchor_n or anchor_c): protein_residues = residues[residues.polymer_types == Residue.PT_AMINO] if not len(protein_residues): raise UserError( 'N- and/or C-terminal anchors were specified, but ' 'the copied selection does not contain any amino acid residues!' ) import numpy fm = us[0] m = target_model residues = Residues( sorted(residues, key=lambda r: (r.chain_id, r.number, r.insertion_code))) atoms = residues.atoms coords = atoms.coords atom_map = {} if renumber_from: offset = residues[0].number - renumber_from else: offset = 0 def new_residue_number(r): if r in residues: return r.number - offset return r.number def new_chain_id(r): if r in residues and chain_id: return chain_id return r.chain_id merged_residues = m.residues.merge(residues) merged_residues = Residues( sorted(merged_residues, key=lambda r: (new_chain_id(r), new_residue_number(r), r.insertion_code))) new_residue_mask = numpy.in1d(merged_residues, residues) new_residue_indices = numpy.argwhere(new_residue_mask).ravel() existing_residue_mask = numpy.in1d(merged_residues, m.residues) existing_residue_indices = numpy.argwhere(existing_residue_mask).ravel() insertion_point_map = {} if chain_id is not None: cids = [chain_id] else: cids = residues.unique_chain_ids for cid in cids: existing_residues = m.residues[m.residues.chain_ids == cid] if not len(existing_residues): insertion_point_map[cid] = None continue existing_residue_numbers = numpy.array( [str(r.number) + r.insertion_code for r in existing_residues]) cres = residues[residues.chain_ids == cid] new_residue_numbers = numpy.array( [str(r.number - offset) + r.insertion_code for r in cres]) duplicate_flags = numpy.in1d(new_residue_numbers, existing_residue_numbers) if numpy.any(duplicate_flags): dup_residues = cres[duplicate_flags] err_str = ( 'The requested merge could not be completed because the ' 'following residues in chain {} (after applying any renumbering) ' 'will have the same residue numbers as existing residues in ' 'the target: {}').format( cid, ', '.join( str(r.number) + r.insertion_code for r in dup_residues)) raise UserError(err_str) chain_mask = (numpy.array([new_chain_id(r) for r in merged_residues]) == cid) new_r_in_chain_mask = numpy.logical_and(chain_mask, new_residue_mask) insertion_point_map[cid] = None if numpy.any(new_r_in_chain_mask): last_new_r_index = numpy.argwhere(new_r_in_chain_mask)[-1] greater_indices = existing_residue_indices[ existing_residue_indices > last_new_r_index] if len(greater_indices): insertion_point_map[cid] = merged_residues[greater_indices[0]] current_residue = None first_index = new_residue_indices[0] if first_index > 0: prev_res = merged_residues[first_index - 1] else: prev_res = None prev_new_res = None from chimerax.atomic.struct_edit import add_bond for merged_index, r in zip(new_residue_indices, residues): if chain_id: cid = chain_id else: cid = r.chain_id precedes = insertion_point_map[cid] insertion_code = r.insertion_code if insertion_code == '': insertion_code = ' ' nr = m.new_residue(r.name, cid, r.number - offset, insert=insertion_code, precedes=precedes) nr.ribbon_hide_backbone = r.ribbon_hide_backbone nr.ribbon_display = r.ribbon_display nr.ribbon_color = r.ribbon_color nr.ss_type = r.ss_type for a in r.atoms: na = atom_map[a] = m.new_atom(a.name, a.element) na.coord = a.coord na.bfactor = a.bfactor na.aniso_u6 = a.aniso_u6 na.occupancy = a.occupancy na.draw_mode = a.draw_mode na.color = a.color na.display = a.display nr.add_atom(na) for n in a.neighbors: nn = atom_map.get(n, None) if nn is not None: add_bond(na, nn) if prev_res is not None: if (r.polymer_type == Residue.PT_AMINO and prev_res not in nr.neighbors and prev_res.chain_id == cid and prev_res.polymer_type == Residue.PT_AMINO): if precedes is not None: if precedes.chain_id == cid and precedes.polymer_type == Residue.PT_AMINO: ratoms = prev_res.atoms.merge(precedes.atoms) pc = prev_res.find_atom('C') nn = nr.find_atom('N') if precedes is not None and precedes.polymer_type == Residue.PT_AMINO and precedes.chain_id == cid: nc = nr.find_atom('C') pn = precedes.find_atom('N') prev_res = nr new_atoms = Atoms(list(atom_map.values())) if transform is not None: # Using Atoms.transform() rather than simply transforming the coords, # because this also correctly transforms any anisotropic B-factors. new_atoms.transform(transform) if anchor_n: anchor_atom = anchor_n.find_atom('C') link_atom = atom_map[protein_residues[0].find_atom('N')] _remove_excess_terminal_atoms(anchor_atom) _remove_excess_terminal_atoms(link_atom) add_bond(anchor_atom, link_atom) for r in new_atoms.unique_residues: merged_atoms = anchor_n.atoms.merge(r.atoms) if anchor_c: anchor_atom = anchor_c.find_atom('N') link_atom = atom_map[protein_residues[-1].find_atom('C')] _remove_excess_terminal_atoms(anchor_atom) _remove_excess_terminal_atoms(link_atom) add_bond(anchor_atom, link_atom) new_atoms.displays = True return new_atoms
def create_merged_residue(residues, base_residue=None, new_name=None): if base_residue is None: base_residue = residues[0] if base_residue not in residues: raise TypeError('base_residue must be a member of residues!') if new_name is None: new_name = base_residue.name from chimerax.atomic import Residues from chimerax.atomic.struct_edit import add_atom, add_bond residues = Residues(residues) seen = set() for r in residues: for n in r.neighbors: seen.add(n) if not all([r in seen for r in residues]): raise TypeError( 'All residues must be connected through covalent bonds!') atoms = residues.atoms other_residues = residues.subtract(Residues([base_residue])) bonds = atoms.intra_bonds external_bonds = [] for r in seen: if r not in other_residues: for n in r.neighbors: if n in residues: external_bonds.extend(r.bonds_between(n)) next_atom_number = highest_atom_number(base_residue.atoms) + 1 atom_map = {} other_atoms = other_residues.atoms # Do heavy atoms first, so we can assign hydrogen names based on them other_heavy_atoms = other_atoms[other_atoms.element_names != 'H'] for a in other_heavy_atoms: name = a.element.name + str(next_atom_number) next_atom_number += 1 atom_map[a] = add_atom(name, a.element, base_residue, a.coord, occupancy=a.occupancy, bfactor=a.bfactor) # Add the hydrogens for a, na in atom_map.items(): base_number = na.name[1:] i = 1 for n in a.neighbors: if n.element.name == 'H': name = 'H' + base_number + str(i) i += 1 add_atom(name, n.element, base_residue, n.coord, bonded_to=na, occupancy=n.occupancy, bfactor=n.bfactor) # Add all internal bonds for a, na in atom_map.items(): for n in a.neighbors: nn = atom_map.get(n, None) if nn is not None and nn not in na.neighbors: add_bond(na, nn) # Now we just need to add the bonds between base_residue and the merged portion, and any # other bonds between the merged portion and the wider model. To avoid valence errors we'll need # to first delete the existing bonds. for b in external_bonds: atoms = b.atoms if atoms[1] in atom_map.keys(): atoms = atoms[::-1] na = atom_map[atoms[0]] a = atoms[1] b.delete() add_bond(na, a) other_residues.delete() base_residue.name = new_name