Example #1
0
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)
Example #2
0
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)
Example #3
0
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)
Example #4
0
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)
Example #5
0
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)
Example #6
0
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
Example #7
0
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)
Example #8
0
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)
Example #9
0
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
Example #10
0
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
Example #11
0
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
Example #12
0
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
Example #13
0
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
Example #14
0
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