def make_fmet_from_met(session, residue): err_string = "make_fmet_from_met() is only applicable to N-terminal methionine residues!" if residue.name != 'MET': raise TypeError(err_string) n = residue.find_atom('N') if n is None: raise TypeError('Missing N atom! Is your residue complete?') for nb in n.neighbors: if nb.residue != residue: raise TypeError(err_string) if nb.element.name == 'H': nb.delete() from chimerax.mmcif import find_template_residue import numpy fme = find_template_residue(session, 'FME') ref_atoms = ('C', 'CA', 'N') r_coords = numpy.array([residue.find_atom(a).coord for a in ref_atoms]) t_coords = numpy.array([fme.find_atom(a).coord for a in ref_atoms]) from chimerax.geometry import align_points tf, rms = align_points(t_coords, r_coords) from chimerax.atomic.struct_edit import add_atom atom_pairs = (('N', 'H'), ('N', 'CN'), ('CN', 'O1'), ('CN', 'HCN')) for (bname, aname) in atom_pairs: ta = fme.find_atom(aname) add_atom(aname, ta.element, residue, tf * ta.coord, bonded_to=residue.find_atom(bname)) residue.name = 'FME'
def add_hydrogen_to_atom(atom, coord, name=None): ''' Add a single hydrogen atom to the given heavy atom, at the given coordinate. Simple-minded tool, taking no notice of chemistry or geometry. ''' from chimerax.atomic import Element r = atom.residue s = atom.structure if name is not None: if name in r.atoms.names: raise TypeError('This atom name is already taken!') else: existing_names = [ a.name for a in atom.neighbors if a.element.name == 'H' ] if len(existing_names): last_digits = [ int(n[-1]) for n in existing_names if n[-1].isdigit() ] if len(last_digits): num = max(last_digits) + 1 else: num = 1 else: num = 1 name = 'H' + atom.name[1:] + str(num) from chimerax.atomic.struct_edit import add_atom na = add_atom(name, Element.get_element('H'), r, coord, bonded_to=atom) return na
def place_helium(structure, res_name, position=None): '''If position is None, place in the center of view''' max_existing = 0 for r in structure.residues: if r.chain_id == "het" and r.number > max_existing: max_existing = r.number res = structure.new_residue(res_name, "het", max_existing + 1) if position is None: if len(structure.session.models) == 0: position = (0.0, 0.0, 0.0) else: #view = structure.session.view #n, f = view.near_far_distances(view.camera, None) #position = view.camera.position.origin() + (n+f) * view.camera.view_direction() / 2 # apparently the commented-out code above is equivalent to... position = structure.session.main_view.center_of_rotation from numpy import array position = array(position) from chimerax.atomic.struct_edit import add_atom helium = Element.get_element("He") a = add_atom("He", helium, res, position) from chimerax.atomic.colors import element_color a.color = element_color(helium.number) a.draw_mode = a.BALL_STYLE return a
def new_hydrogen(parent_atom, h_num, total_hydrogens, naming_schema, pos, parent_type_info, alt_loc): global _serial, _metals nearby_metals = _metals.search(pos, _metal_dist) if _metal_dist > 0.0 else [] for metal in nearby_metals: if metal.structure != parent_atom.structure: continue metal_pos = metal.coord parent_pos = parent_atom.coord if metal_clash(metal_pos, pos, parent_pos, parent_atom, parent_type_info): return # determine added H color before actually adding it... h_color = determine_h_color(parent_atom) new_h = add_atom(_h_name(parent_atom, h_num, total_hydrogens, naming_schema), "H", parent_atom.residue, pos, serial_number=_serial, bonded_to=parent_atom, alt_loc=alt_loc) _serial = new_h.serial_number + 1 new_h.color = h_color new_h.hide = parent_atom.hide return new_h
def place_metal_at_coord(model, chain_id, residue_number, residue_name, atom_name, coord, element_name=None, bfactor=20): ''' Create a new residue encompassing a single metal ion in the current model, and place it at the given coordinate. ''' if element_name is None: element_name = atom_name.title() from chimerax.atomic import Element from chimerax.atomic.struct_edit import add_atom e = Element.get_element(element_name) r = model.new_residue(residue_name, chain_id, residue_number) add_atom(atom_name, e, r, coord, bfactor=bfactor) return r
def cmd_centroid(session, atoms=None, *, mass_weighting=False, name="centroid", color=None, radius=2.0): """Wrapper to be called by command line. Use chimerax.centroids.centroid for other programming applications. """ from chimerax.core.errors import UserError from chimerax.atomic import AtomicStructure, concatenate, Structure if atoms is None: structures_atoms = [m.atoms for m in session.models if isinstance(m, AtomicStructure)] if structures_atoms: atoms = concatenate(structures_atoms) else: raise UserError("Atom specifier selects no atoms") structures = atoms.unique_structures if len(structures) > 1: crds = atoms.scene_coords else: crds = atoms.coords if mass_weighting: masses = atoms.elements.masses avg_mass = masses.sum() / len(masses) import numpy weights = masses[:, numpy.newaxis] / avg_mass else: weights = None xyz = centroid(crds, weights=weights) s = Structure(session, name=name) r = s.new_residue('centroid', 'centroid', 1) from chimerax.atomic.struct_edit import add_atom a = add_atom('cent', 'C', r, xyz) if color: a.color = color.uint8x4() else: from chimerax.atomic.colors import element_color, predominant_color color = predominant_color(atoms) if color is None: a.color = element_color(a.element.number) else: a.color = color a.radius = radius if len(structures) > 1: session.models.add([s]) else: structures[0].add([s]) session.logger.info("Centroid '%s' placed at %s" % (name, xyz)) return a
def complete_terminal_carboxylate(session, cter): from chimerax.atomic.bond_geom import bond_positions from chimerax.atomic.struct_edit import add_atom from chimerax.atomic import Element if cter.find_atom("OXT"): return c = cter.find_atom("C") if c: if c.num_bonds != 2: return loc = bond_positions(c.coord, 3, 1.229, [n.coord for n in c.neighbors])[0] oxt = add_atom("OXT", Element.get_element("O"), cter, loc, bonded_to=c) from chimerax.atomic.colors import element_color if c.color == element_color(c.element.number): oxt.color = element_color(oxt.element.number) else: oxt.color = c.color session.logger.info("Missing OXT added to C-terminal residue %s" % str(cter))
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 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 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 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 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 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 _read_block(session, stream, line_number): # XYZ files are stored in blocks, with each block representing # a set of atoms. This function reads a single block # and builds a ChimeraX AtomStructure instance containing # the atoms listed in the block. # First line should be an integer count of the number of # atoms in the block. count_line = stream.readline() if not count_line: # Reached EOF, normal termination condition return None, line_number line_number += 1 try: count = int(count_line) except ValueError: session.logger.error("line %d: atom count missing" % line_number) return None, line_number # Create the AtomicStructure instance for atoms in this block. # All atoms in the structure are placed in one residue # since XYZ format does not partition atoms into groups. from chimerax.atomic import AtomicStructure from numpy import array, float64 s = AtomicStructure(session) residue = s.new_residue("UNK", 'A', 1) # XYZ format supplies the atom element type only, but # ChimeraX keeps track of both the element type and # a unique name for each atom. To construct the unique # atom name, the # 'element_count' dictionary is used # to track the number of atoms of each element type so far, # and the current count is used to build unique atom names. element_count = {} # Next line is a comment line s.comment = stream.readline().strip() line_number += 1 # import convenience function for adding atoms from chimerax.atomic.struct_edit import add_atom # There should be "count" lines of atoms. for n in range(count): atom_line = stream.readline() if not atom_line: session.logger.error("line %d: atom data missing" % line_number) return None, line_number line_number += 1 # Extract available data parts = atom_line.split() if len(parts) != 4: session.logger.error("line %d: atom data malformatted" % line_number) return None, line_number # Convert to required parameters for creating atom. # Since XYZ format only required atom element, we # create a unique atom name by putting a number after # the element name. xyz = [float(v) for v in parts[1:]] element = parts[0] n = element_count.get(element, 0) + 1 name = element + str(n) element_count[element] = n # Create atom in AtomicStructure instance 's', # set its coordinates, and add to residue atom = add_atom(name, element, residue, array(xyz, dtype=float64)) # Use AtomicStructure method to add bonds based on interatomic distances s.connect_structure() # Return AtomicStructure instance and current line number return s, line_number
def read_sdf(session, stream, file_name): path = stream.name if hasattr(stream, 'name') else None structures = [] nonblank = False state = "init" from chimerax.core.errors import UserError from chimerax.atomic.struct_edit import add_atom from chimerax.atomic import AtomicStructure, Element, Bond, Atom, AtomicStructure from numpy import array Bond.register_attr(session, "order", "SDF format", attr_type=float) Atom.register_attr(session, "charge", "SDF format", attr_type=float) AtomicStructure.register_attr(session, "charge_model", "SDF format", attr_type=str) try: for l in stream: line = l.strip() nonblank = nonblank or line if state == "init": state = "post header 1" mol_name = line elif state == "post header 1": state = "post header 2" elif state == "post header 2": state = "counts" elif state == "counts": if not line: break state = "atoms" serial = 1 anums = {} atoms = [] try: num_atoms = int(l[:3].strip()) num_bonds = int(l[3:6].strip()) except ValueError: raise UserError("Atom/bond counts line of MOL/SDF file '%s' is botched" % file_name) from chimerax.atomic.structure import is_informative_name name = mol_name if is_informative_name(mol_name) else file_name s = AtomicStructure(session, name=name) structures.append(s) r = s.new_residue("UNL", " ", 1) elif state == "atoms": num_atoms -= 1 if num_atoms == 0: if num_bonds: state = "bonds" else: state = "properties" try: x = float(l[:10].strip()) y = float(l[10:20].strip()) z = float(l[20:30].strip()) elem = l[31:34].strip() except ValueError: s.delete() raise UserError("Atom line of MOL/SDF file '%s' is not x y z element...: '%s'" % (file_name, l)) element = Element.get_element(elem) if element.number == 0: # lone pair of somesuch atoms.append(None) continue anum = anums.get(element.name, 0) + 1 anums[element.name] = anum a = add_atom("%s%d" % (element.name, anum), element, r, array([x,y,z]), serial_number=serial) serial += 1 atoms.append(a) elif state == "bonds": num_bonds -= 1 if num_bonds == 0: state = "properties" try: a1_index = int(l[:3].strip()) a2_index = int(l[3:6].strip()) order = float(l[6:9].strip()) except ValueError: raise UserError("Bond line of MOL/SDF file '%s' is not a1 a2 order...: '%s'" % (file_name, 1)) a1 = atoms[a1_index-1] a2 = atoms[a2_index-1] if not a1 or not a2: continue s.new_bond(a1, a2).order = order elif state == "properties": if not s.atoms: raise UserError("No atoms found for compound '%s' in MOL/SDF file '%s'" % (name, file_name)) if line.split() == ["M", "END"]: state = "data" reading_data = None elif state == "data": if line == "$$$$": nonblank = False state = "init" elif reading_data == "charges": data_item = line.strip() if data_item: try: data.append(float(data_item)) except ValueError: try: index, charge = data_item.split() index = int(index) - 1 charge = float(charge) except ValueError: raise UserError("Charge data (%s) in %s data is not either a floating-point" " number or an atom index and a floating-point number" % (data_item, orig_data_name)) else: if not indexed_charges: # for indexed charges, the first thing is a count data.pop() indexed_charges = True data.append((index, charge)) else: if not indexed_charges and len(atoms) != len(data): raise UserError("Number of charges (%d) in %s data not equal to number of atoms" " (%d)" % (len(data), orig_data_name, len(atoms))) if indexed_charges: for a in atoms: # charge defaults to 0.0, so don't need to set non-indexed for index, charge in data: atoms[index].charge = charge else: for a, charge in zip(atoms, data): a.charge = charge if "mmff94" in data_name: s.charge_model = "MMFF94" reading_data = None elif reading_data == "cid": data_item = line.strip() if data_item: try: cid = int(data_item) except ValueError: raise UserError("PubChem CID (%s) is %s data is not an integer" % (data_item, orid_data_name)) s.name = "pubchem:%d" % cid s.prefix_html_title = False s.get_html_title = lambda *args, cid=cid: 'PubChem entry <a href="https://pubchem.ncbi.nlm.nih.gov/compound/%d">%d</a>' % (cid, cid) s.has_formatted_metadata = lambda *args: False reading_data = None elif line.startswith('>'): try: lp = line.index('<') rp = line[lp+1:].index('>') + lp + 1 except (IndexError, ValueError): continue orig_data_name = line[lp+1:rp] data_name = orig_data_name.lower() if data_name.endswith("charges") and "partial" in data_name: reading_data = "charges" indexed_charges = False data = [] elif data_name == "pubchem_compound_cid": reading_data = "cid" except BaseException: for s in structures: s.delete() raise finally: stream.close() if nonblank and state not in ["data", "init"]: if structures: session.logger.warning("Extraneous text after final $$$$ in MOL/SDF file '%s'" % file_name) else: raise UserError("Unexpected end of file (parser state: %s) in MOL/SDF file '%s'" % (state, file_name)) return structures, ""
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
def place_peptide(structure, sequence, phi_psis, *, position=None, rot_lib=None, chain_id=None): """ Place a peptide sequence. *structure* is an AtomicStructure to add the peptide to. *sequence* contains the sequence as a string. *phi_psis* is a list of phi/psi tuples, one per residue. *position* is either an array or sequence specifying an xyz world coordinate position or None. If None, the peptide is positioned at the center of the view. *rot_lib* is the name of the rotamer library to use to position the side chains. session.rotamers.library_names lists installed rotamer library names. If None, a default library will be used. *chain_id* is the desired chain ID for the peptide. If None, earliest alphabetical chain ID not already in use in the structure will be used (upper case taking precedence over lower case). returns a Residues collection of the added residues """ if not sequence: raise PeptideError("No sequence supplied") sequence = sequence.upper() if not sequence.isupper(): raise PeptideError("Sequence contains non-alphabetic characters") from chimerax.atomic import Sequence for c in sequence: try: r3 = Sequence.protein1to3[c] except KeyError: raise PeptideError("Unrecognized protein 1-letter code: %s" % c) if r3[-1] == 'X': raise PeptideError( "Peptide sequence cannot contain ambiguity codes (i.e. '%s')" % c) if len(sequence) != len(phi_psis): raise PeptideError("Number of phi/psis not equal to sequence length") session = structure.session open_models = session.models[:] if len(open_models) == 0: need_focus = True elif len(open_models) == 1: if open_models[0] == structure: need_focus = not structure.atoms else: need_focus = False else: need_focus = False if position is None: position = session.main_view.center_of_rotation from numpy import array position = array(position) prev = [None] * 3 pos = 1 from chimerax.atomic.struct_edit import DIST_N_C, DIST_CA_N, DIST_C_CA, DIST_C_O, \ find_pt, add_atom, add_dihedral_atom serial_number = None residues = [] prev_psi = 0 if chain_id is None: chain_id = unused_chain_id(structure) from numpy import array for c, phi_psi in zip(sequence, phi_psis): phi, psi = phi_psi while structure.find_residue(chain_id, pos): pos += 1 r = structure.new_residue(Sequence.protein1to3[c], chain_id, pos) residues.append(r) for backbone, dist, angle, dihed in [('N', DIST_N_C, 116.6, prev_psi), ('CA', DIST_CA_N, 121.9, 180.0), ('C', DIST_C_CA, 110.1, phi)]: if prev[0] is None: pt = array([0.0, 0.0, 0.0]) elif prev[1] is None: pt = array([dist, 0.0, 0.0]) elif prev[2] is None: pt = find_pt(prev[0].coord, prev[1].coord, array([0.0, 1.0, 0.0]), dist, angle, 0.0) else: pt = find_pt(prev[0].coord, prev[1].coord, prev[2].coord, dist, angle, dihed) a = add_atom(backbone, backbone[0], r, pt, serial_number=serial_number, bonded_to=prev[0]) serial_number = a.serial_number + 1 prev = [a] + prev[:2] o = add_dihedral_atom("O", "O", prev[0], prev[1], prev[2], DIST_C_O, 120.4, 180 + psi, bonded=True) prev_psi = psi # C terminus O/OXT at different angle than mainchain O structure.delete_atom(o) add_dihedral_atom("O", "O", prev[0], prev[1], prev[2], DIST_C_O, 117.0, 180 + psi, bonded=True) add_dihedral_atom("OXT", "O", prev[0], prev[1], prev[2], DIST_C_O, 117.0, psi, bonded=True) from chimerax.atomic import Residues residues = Residues(residues) from chimerax.swap_res import swap_aa # swap_aa is capable of swapping all residues in one call, but need to process one by one # since side-chain clashes are only calculated against pre-existing side chains kw = {} if rot_lib: kw['rot_lib'] = rot_lib for r in residues: swap_aa(session, [r], "same", criteria="cp", log=False, **kw) # find peptide center atoms = residues.atoms coords = atoms.coords center = coords.mean(0) correction = position - center atoms.coords = coords - correction from chimerax.std_commands.dssp import compute_ss compute_ss(session, structure) if need_focus: from chimerax.core.commands import run run(session, "view") return residues