#2334 closed defect (fixed)
Residues added to N-terminus of a chain do not appear in Model.polymers
Reported by: | Tristan Croll | Owned by: | pett |
---|---|---|---|
Priority: | major | Milestone: | |
Component: | Structure Editing | Version: | |
Keywords: | Cc: | ||
Blocked By: | Blocking: | ||
Notify when closed: | Platform: | all | |
Project: | ChimeraX |
Description
If I use the (new today, still a bit messy) code below (add_amino_acid_residue
) to do a quick-and-dirty addition of a new residue to the N-terminus of an existing chain, then *most* things work - bonds and cartoons draw correctly, and simulations run - but the new residue doesn't appear in Model.polymers(missing_structure_treatment=Model.PMS_NEVER_CONNECTS)
.
Code below. The call is something like add_amino_acid_residue(model, 'PHE', next_res=selected_residues(session)[0])
.
While playing around with attempts to debug this, I also noticed that Model.reorder_residues()
segfaults. Will raise a separate ticket.
def set_new_atom_style(session, atoms): from chimerax.atomic import selected_atoms, Atom from chimerax.core.commands import run atoms.draw_modes = Atom.STICK_STYLE current_sel = selected_atoms(session) session.selection.clear() atoms.selected = True run(session, "color sel bychain; color sel byhetero") session.selection.clear() current_sel.selected = True def fix_amino_acid_protonation_state(residue): if residue.name not in ('GLU', 'ASP'): raise TypeError('This method is only applicable to GLU and ASP residues!') if residue.name == 'GLU': a = residue.find_atom('HE2') if a is not None: a.delete() elif residue.name == 'ASP': a = residue.find_atom('HD2') if a is not None: a.delete() def new_residue_from_template(model, template, chain_id, center, residue_number=None, insert_code = ' ', b_factor = 50): ''' Create a new residue based on a template, and add it to the model. ''' if residue_number is None: if chain_id in m.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) for i, ta in enumerate(template.atoms): a = tatom_to_atom[ta] = model.new_atom(ta.name, ta.element) a.coord = t_coords[i] a.bfactor = b_factor r.add_atom(a) for tn in ta.neighbors: n = tatom_to_atom.get(tn, None) if n is not None: model.new_bond(a, n) set_new_atom_style(model.session, r.atoms) return r def add_amino_acid_residue(model, resname, prev_res=None, next_res=None, chain_id=None, number=None, center=None, insertion_code=' ', b_factor=50, occupancy=1, phi=-135, psi=135): session = model.session if (not chain_id or not number or not center) 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!') if prev_res: for n in prev_res.find_atom('C').neighbors: if n.residue != prev_res: raise TypeError('This residue already has another bonded to its ' 'C terminus!') chain_id = prev_res.chain_id oxt = prev_res.find_atom('OXT') if oxt is not None: oxt.delete() elif next_res: for n in next_res.find_atom('N').neighbors: if n.residue != next_res: raise TypeError('This residue already has another bonded to its ' 'N terminus!') 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.atomic 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) r.atoms[numpy.in1d(r.atoms.names, ['OXT', 'HXT', 'H2'])].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) else: from chimerax.core.geometry import align_points if prev_res: model.new_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: model.new_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) target_coords = numpy.array([c_pos, ca_pos, o_pos]) align_coords = numpy.array([r.find_atom(a).coord for a in ['C', 'CA', 'O']]) tf = align_points(align_coords, target_coords)[0] r.atoms.coords = tf*r.atoms.coords 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 set_new_atom_style(session, r.atoms) return r def _find_next_N_position(prev_res): from chimerax.atomic.struct_edit import find_pt bond_length = 1.34 angle = 120 dihedral = 180 a1 = prev_res.find_atom('C') a2 = prev_res.find_atom('CA') a3 = prev_res.find_atom('O') return find_pt(*[a.coord for a in [a1, a2, a3]], bond_length, angle, dihedral) def _find_next_CA_position(n_pos, prev_res): from chimerax.atomic.struct_edit import find_pt bond_length = 1.48 angle = 124 omega = 180 c = prev_res.find_atom('C') ca = prev_res.find_atom('CA') return find_pt(n_pos, *[a.coord for a in [c, ca]], bond_length, angle, omega) def _find_next_C_position(ca_pos, n_pos, prev_res, phi): from chimerax.atomic.struct_edit import find_pt bond_length = 1.53 angle = 120 c = prev_res.find_atom('C') return find_pt(ca_pos, n_pos, c.coord, bond_length, angle, phi) def _find_prev_C_position(next_res, psi): from chimerax.atomic.struct_edit import find_pt bond_length = 1.34 angle = 120 a1 = next_res.find_atom('N') a2 = next_res.find_atom('CA') a3 = next_res.find_atom('C') return find_pt(*[a.coord for a in [a1, a2, a3]], bond_length, angle, psi) def _find_prev_CA_position(c_pos, next_res): from chimerax.atomic.struct_edit import find_pt bond_length = 1.53 angle = 120 omega = 180 n = next_res.find_atom('N') ca = next_res.find_atom('CA') return find_pt(c_pos, *[a.coord for a in [n, ca]], bond_length, angle, omega) def _find_prev_O_position(c_pos, next_res): from chimerax.atomic.struct_edit import find_pt bond_length = 1.22 angle = 120 dihedral = 0 n = next_res.find_atom('N') ca = next_res.find_atom('CA') return find_pt(c_pos, *[a.coord for a in (n, ca)], bond_length, angle, dihedral)
Change History (4)
comment:1 by , 6 years ago
Status: | assigned → accepted |
---|
comment:2 by , 6 years ago
Status: | accepted → feedback |
---|
comment:3 by , 6 years ago
Resolution: | → fixed |
---|---|
Status: | feedback → closed |
Ah - sorry about the unreproducability! I tried to make it self-contained by bundling in new_residue_from_template()
, but forgot to remove the import statement. Anyway, it looks like this was fixed by your addition of the precedes
keyword to new_residue()
(an aside: for the sake of code simplicity it might be nice to also have a follows
keyword...?). Anyway, this code is all in the ISOLDE builds I released last night:
from chimerax.core.commands import open as cxopen m = cxopen.open(session, '3io0')[0] from chimerax.isolde.atomic.building.build_utils import add_amino_acid_residue r = add_amino_acid_residue(m, 'ALA', next_res=m.residues[0]) pols = m.polymers(missing_structure_treatment=m.PMS_NEVER_CONNECTS) pols Out[6]: [(<chimerax.atomic.molarray.Residues at 0x7fa01872ef90>, 1)] r in pols[0][0] Out[7]: True
comment:4 by , 6 years ago
I typically don't like to add mutually exclusive keywords to a command. The only time you need 'follows' is at the end of all residues, which is the same as omitting the 'precedes' keyword. I guess I do understand the convenience in some usage scenarios.
I am having trouble reproducing this. Certainly, if I take a structure with two polymer chains and form a polymeric bond between them, I get one polymer as the result. I could not use the code you provided because it is not complete. Namely:
from .place_ligand import new_residue_from_template
Perhaps you could provide a self-contained example that reproduces the error? (Preferably as an attachment rather than inline code!)
--Eric