Exemple #1
0
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'
Exemple #2
0
 def get_ccd_template_and_name(session, tname):
     from ..openmm.amberff.template_utils import template_name_to_ccd_name
     from chimerax import mmcif
     ccd_name, extra_info = template_name_to_ccd_name(tname)
     if ccd_name is None:
         return (None, '')
     try:
         tmpl = mmcif.find_template_residue(session, ccd_name)
     except ValueError:
         return (None, "No CCD template found")
     description = tmpl.description
     if extra_info is not None:
         description += ', ' + extra_info
     return (tmpl, description)
Exemple #3
0
def match_ff_templates_to_ccd_templates(session, forcefield, ccd_names):
    '''
    For each template in the MD forcefield, try to find the CCD template that
    most closely matches it. This will take a long time!
    '''
    from chimerax.isolde.graph import make_graph_from_residue_template
    from chimerax import mmcif
    ff_templates = forcefield._templates
    best_matches = {}
    ccd_graphs = {}
    small_ccd_templates = []
    for name in ccd_names:
        ccd_template = mmcif.find_template_residue(session, name)
        if len(ccd_template.atoms) > 2:
            ccd_graphs[name] = make_graph_from_residue_template(ccd_template)
        else:
            small_ccd_templates.append(ccd_template)
    for tname, template in ff_templates.items():
        best_score = -1000
        if len(template.atoms) < 4:
            best_match, score = match_small_ff_template_to_ccd_templates(
                template, small_ccd_templates)
            best_matches[tname] = (best_match, score)
            continue
        tgraph = template.graph
        num_heavy_atoms = len(tgraph.labels[tgraph.labels != 1])
        for ccd_name, ccd_graph in ccd_graphs.items():
            ccd_heavy_atoms = len(ccd_graph.labels[ccd_graph.labels != 1])
            if abs(ccd_heavy_atoms - num_heavy_atoms) > 2:
                continue
            fti, cti, _ = tgraph.maximum_common_subgraph(ccd_graph)
            score = len(fti) * 3 - len(tgraph.labels) - len(ccd_graph.labels)
            if score > best_score:
                best_matches[tname] = ([ccd_name], score)
                best_score = score
            elif score == best_score:
                best_matches[tname][0].append(ccd_name)
        print('Best matches for {}: {}'.format(
            tname, ', '.join(best_matches[tname][0])),
              flush=True)
    return best_matches
Exemple #4
0
def fix_residue_to_match_md_template(session,
                                     residue,
                                     md_template,
                                     cif_template=None,
                                     rename=False):
    '''
    For the given residue, add/remove atoms as necessary to match the given MD
    template. If no explicit cif_template argument is given, an attempt will be
    made to find a match using ISOLDE's database. If no CIF template is found,
    corrections to the residue will be limited to deleting excess atoms.
    '''
    import numpy
    from chimerax.isolde.openmm.amberff.template_utils import (
        template_name_to_ccd_name, match_template_atoms_to_ccd_atoms)
    if cif_template is None:
        ccd_name, _ = template_name_to_ccd_name(md_template.name)
        if ccd_name is not None:
            from chimerax import mmcif
            try:
                cif_template = mmcif.find_template_residue(session, ccd_name)
            except ValueError:
                pass
    else:
        ccd_name = cif_template.name
    if cif_template is None:
        return trim_residue_to_md_template(residue, md_template)
    template_indices, ccd_indices = match_template_atoms_to_ccd_atoms(
        session, md_template, ccd_name)
    fix_residue_from_template(residue,
                              cif_template,
                              template_indices=ccd_indices)
    from chimerax.atomic import Atoms
    ratoms = Atoms(
        [residue.find_atom(cif_template.atoms[i].name) for i in ccd_indices])
    residue_indices = residue.atoms.indices(ratoms)
    #template_extra_atoms = [md_template.atoms[i] for i in template_extra_indices]
    add_missing_md_template_atoms(session, residue, md_template,
                                  residue_indices, template_indices)
Exemple #5
0
def match_template_atoms_to_ccd_atoms(session,
                                      template,
                                      ccd_name=None,
                                      timeout=5):
    if ccd_name is None:
        ccd_name, _ = template_name_to_ccd_name(template.name)
    if ccd_name is None:
        raise TypeError('MD template {} has no match in the CCD!'.format(
            template.name))
    matches = _template_atom_to_ccd_atom_cache.get((template.name, ccd_name))
    if matches is not None:
        return matches
    from chimerax import mmcif
    ccd_template = mmcif.find_template_residue(session, ccd_name)
    if len(template.atoms) > 2:
        # Try by name first (typically very fast)
        from chimerax.isolde.graph import make_graph_from_residue_template
        tgraph = template.graph
        ccd_graph = make_graph_from_residue_template(ccd_template)
        ti, ci, timed_out = tgraph.maximum_common_subgraph(ccd_graph,
                                                           big_first=True,
                                                           timeout=timeout)
        if timed_out:
            ti_2, ci_2, to_2 = tgraph.maximum_common_subgraph(ccd_graph,
                                                              timeout=timeout)
            if len(ti_2) > len(ti):
                ti = ti_2
                ci = ci_2
                if to_2:
                    warn_str = (
                        'Graph matching of MD template {} to CCD template {}'
                        ' timed out. Match may not be optimal').format(
                            template.name, ccd_name)
                    session.logger.warning(warn_str)
        _template_atom_to_ccd_atom_cache[(template.name, ccd_name)] = (ti, ci)
        return ti, ci
    import numpy
    ti = []
    ci = []
    for i, a in enumerate(template.atoms):
        ti.append(i)
        candidates = []
        for j, ca in enumerate(ccd_template.atoms):
            if j not in ci:
                if ca.element.number == a.element.atomic_number:
                    candidates.append((j, ca))
        if not len(candidates):
            raise TypeError(
                'MD Template {} does not match CCD template {}!'.format(
                    template.name, ccd_template.name))
        elif len(candidates) == 1:
            ci.append(candidates[0][0])
        else:
            # Check to see if we can distinguish by name
            name_match = False
            for j, ca in candidates:
                if ca.name == a.name:
                    name_match = True
                    candidates.append(j)
                    break
            if not name_match:
                # Just pick the first
                ci.append(candidates[0][0])
    ti = numpy.array(ti)
    ci = numpy.array(ci)
    _template_atom_to_ccd_atom_cache[(template.name, ccd_name)] = (ti, ci)
    return ti, ci
Exemple #6
0
def replace_residue(session, residue, new_residue_name):
    block_if_sim_running(session)
    from chimerax.core.errors import UserError
    from chimerax.isolde.cmd import isolde_start
    isolde_start(session)
    from chimerax.atomic import Residues
    if isinstance(residue, Residues):
        if len(residue) != 1:
            raise UserError('Must have a single residue selected!')
        residue = residue[0]
    if len(residue.neighbors):
        raise UserError(
            'Replacement by graph matching is currently only '
            'supported for ligands with no covalent bonds to other residues!')
    from ..template_utils import (fix_residue_from_template,
                                  fix_residue_to_match_md_template)
    from chimerax import mmcif
    try:
        cif_template = mmcif.find_template_residue(session, new_residue_name)
    except:
        err_str = (
            'Could not find a mmCIF template for residue name {}. '
            'For novel residues not in the Chemical Components Dictionary, '
            'you will need to provide this first.').format(new_residue_name)
        raise UserError(err_str)
    fix_residue_from_template(residue,
                              cif_template,
                              rename_residue=True,
                              match_by='element')
    ff = session.isolde.forcefield_mgr[session.isolde.sim_params.forcefield]
    ligand_db = session.isolde.forcefield_mgr.ligand_db(
        session.isolde.sim_params.forcefield)
    from chimerax.isolde.openmm.openmm_interface import find_residue_templates
    from chimerax.atomic import Residues
    tdict = find_residue_templates(Residues([residue]),
                                   ff,
                                   ligand_db=ligand_db,
                                   logger=session.logger)
    md_template_name = tdict.get(0)
    if md_template_name is not None:
        fix_residue_to_match_md_template(session,
                                         residue,
                                         ff._templates[md_template_name],
                                         cif_template=cif_template)
    from chimerax.atomic import Atoms, Atom
    chiral_centers = Atoms(
        [a for a in residue.atoms if residue.ideal_chirality(a.name) != 'N'])
    if len(chiral_centers):
        warn_str = (
            'Rebuilt ligand {} has chiral centres at atoms {} '
            '(highlighted). Since the current algorithm used to match topologies '
            'is not chirality aware, you should check these sites carefully to '
            'ensure they are sensible. If in doubt, it is best to delete with '
            '"del #{}/{}:{}{}" and replace with "isolde add ligand {}".'
        ).format(residue.name, ','.join(chiral_centers.names),
                 residue.structure.id_string, residue.chain_id, residue.number,
                 residue.insertion_code, residue.name)
        session.selection.clear()
        chiral_centers.selected = True
        chiral_centers.draw_modes = Atom.BALL_STYLE
        from chimerax.isolde.view import focus_on_selection
        focus_on_selection(session, chiral_centers)
        session.logger.warning(warn_str)
Exemple #7
0
def _prep_add(session,
              structures,
              unknowns_info,
              template,
              need_all=False,
              **prot_schemes):
    global _serial
    _serial = None
    atoms = []
    type_info_for_atom = {}
    naming_schemas = {}
    idatm_type = {}  # need this later; don't want a recomp
    hydrogen_totals = {}

    # add missing OXTs of "real" C termini;
    # delete hydrogens of "fake" N termini after protonation
    # and add a single "HN" back on, using same dihedral as preceding residue;
    # delete extra hydrogen of "fake" C termini after protonation
    logger = session.logger
    real_N, real_C, fake_N, fake_C = determine_termini(session, structures)
    logger.info("Chain-initial residues that are actual N"
                " termini: %s" % ", ".join([str(r) for r in real_N]))
    logger.info("Chain-initial residues that are not actual N"
                " termini: %s" % ", ".join([str(r) for r in fake_N]))
    logger.info("Chain-final residues that are actual C"
                " termini: %s" % ", ".join([str(r) for r in real_C]))
    logger.info("Chain-final residues that are not actual C"
                " termini: %s" % ", ".join([str(r) for r in fake_C]))
    for rc in real_C:
        complete_terminal_carboxylate(session, rc)

    # ensure that N termini are protonated as N3+ (since Npl will fail)
    from chimerax.atomic import Sequence
    for nter in real_N + fake_N:
        n = nter.find_atom("N")
        if not n:
            continue
        # if residue wasn't templated, leave atom typing alone
        if Sequence.protein3to1(n.residue.name) == 'X':
            continue
        if not (n.residue.name == "PRO" and n.num_bonds >= 2):
            n.idatm_type = "N3+"

    coordinations = {}
    for struct in structures:
        pbg = struct.pseudobond_group(struct.PBG_METAL_COORDINATION,
                                      create_type=None)
        if not pbg:
            continue
        for pb in pbg.pseudobonds:
            for a in pb.atoms:
                if not need_all and a.structure not in structures:
                    continue
                if not a.element.is_metal:
                    coordinations.setdefault(a, []).append(pb.other_atom(a))

    remaining_unknowns = {}
    type_info_class = type_info['H'].__class__
    from chimerax.atomic import Residue
    for struct in structures:
        for atom in struct.atoms:
            if atom.element.number == 0:
                res = atom.residue
                struct.delete_atom(atom)
        idatm_lookup = {}
        if template:
            template_lookup = {}
            from chimerax.atomic import TmplResidue
            get_template = TmplResidue.get_template
            for res in struct.residues:
                if get_template(res.name):
                    continue
                try:
                    exemplar = template_lookup[res.name]
                except KeyError:
                    from chimerax.mmcif import find_template_residue
                    tmpl = find_template_residue(session, res.name)
                    if not tmpl:
                        continue
                    from chimerax.atomic import AtomicStructure
                    s = AtomicStructure(session)
                    r = exemplar = template_lookup[res.name] = s.new_residue(
                        res.name, 'A', 1)
                    atom_map = {}
                    for ta in tmpl.atoms:
                        if ta.element.number > 1:
                            a = s.new_atom(ta.name, ta.element)
                            a.coord = ta.coord
                            r.add_atom(a)
                            atom_map[ta] = a
                            for tnb in ta.neighbors:
                                if tnb in atom_map:
                                    s.new_bond(a, atom_map[tnb])
                for a in res.atoms:
                    ea = exemplar.find_atom(a.name)
                    if ea:
                        a.idatm_type = ea.idatm_type
            for r in template_lookup.values():
                r.structure.delete()
            template_lookup.clear()

        for atom in struct.atoms:
            atom_type = atom.idatm_type
            idatm_type[atom] = atom_type
            if atom_type in type_info:
                # don't want to ask for idatm_type in middle
                # of hydrogen-adding loop (since that will
                # force a recomp), so remember here
                type_info_for_atom[atom] = type_info[atom_type]
                # if atom is in standard residue but has missing bonds to
                # heavy atoms, skip it instead of incorrectly protonating
                # (or possibly throwing an error if e.g. it's planar)
                # also
                # UNK/N residues will be missing some or all of their side-chain atoms, so
                # skip atoms that would otherwise be incorrectly protonated due to their
                # missing neighbors
                truncated = \
                        atom.is_missing_heavy_template_neighbors(no_template_okay=True) \
                    or \
                        (atom.residue.name in ["UNK", "N"] and atom.residue.polymer_type != Residue.PT_NONE
                        and unk_atom_truncated(atom)) \
                    or \
                        (atom.residue.polymer_type == Residue.PT_NUCLEIC and atom.name == "P"
                        and atom.num_explicit_bonds < 4)

                if truncated:
                    session.logger.warning(
                        "Not adding hydrogens to %s because it is missing heavy-atom"
                        " bond partners" % atom)
                    type_info_for_atom[atom] = type_info_class(
                        4, atom.num_bonds, atom.name)
                else:
                    atoms.append(atom)
                # sulfonamide nitrogens coordinating a metal
                # get an additional hydrogen stripped
                if coordinations.get(atom, []) and atom.element.name == "N":
                    if "Son" in [nb.idatm_type for nb in atom.neighbors]:
                        orig_ti = type_info[atom_type]
                        type_info_for_atom[atom] = orig_ti.__class__(
                            orig_ti.geometry, orig_ti.substituents - 1,
                            orig_ti.description)
                continue
            if atom in unknowns_info:
                type_info_for_atom[atom] = unknowns_info[atom]
                atoms.append(atom)
                continue
            remaining_unknowns.setdefault(atom.residue.name,
                                          set()).add(atom.name)
            # leave remaining unknown atoms alone
            type_info_for_atom[atom] = type_info_class(4, atom.num_bonds,
                                                       atom.name)

        for rname, atom_names in remaining_unknowns.items():
            names_text = ", ".join([nm for nm in atom_names])
            atom_text, obj_text = ("atoms",
                                   "them") if len(atom_names) > 1 else ("atom",
                                                                        "it")
            logger.warning(
                "Unknown hybridization for %s (%s) of residue type %s;"
                " not adding hydrogens to %s" %
                (atom_text, names_text, rname, obj_text))
        naming_schemas.update(
            determine_naming_schemas(struct, type_info_for_atom))

    if need_all:
        from chimerax.atomic import AtomicStructure
        for struct in [
                m for m in session.models if isinstance(m, AtomicStructure)
        ]:
            if struct in structures:
                continue
            for atom in struct.atoms:
                idatm_type[atom] = atom.idatm_type
                if atom.idatm_type in type_info:
                    type_info_for_atom[atom] = type_info[atom.idatm_type]

    for atom in atoms:
        if atom not in type_info_for_atom:
            continue
        bonding_info = type_info_for_atom[atom]
        total_hydrogens = bonding_info.substituents - atom.num_bonds
        for bonded in atom.neighbors:
            if bonded.element.number == 1:
                total_hydrogens += 1
        hydrogen_totals[atom] = total_hydrogens

    schemes = {}
    # HIS and CYS treated as 'unspecified'; use built-in typing
    for scheme_type, res_names, res_check, typed_atoms in [
        ('his', ["HID", "HIE", "HIP"], None, []),
        ('asp', asp_res_names, _asp_check, asp_prot_names),
        ('glu', glu_res_names, _glu_check, glu_prot_names),
        ('lys', ["LYS", "LYN"], _lys_check, ["NZ"]),
        ('cys', ["CYM"], _cys_check, ["SG"])
    ]:
        scheme = prot_schemes.get(scheme_type + '_scheme', None)
        if scheme is None:
            by_name = True
            scheme = {}
        else:
            by_name = False
        if not scheme:
            for s in structures:
                for r in s.residues:
                    if r.name in res_names and res_check and res_check(r):
                        if by_name:
                            scheme[r] = r.name
                        elif scheme_type != 'his':
                            scheme[r] = res_names[0]
                        # unset any explicit typing...
                        for ta in typed_atoms:
                            a = r.find_atom(ta)
                            if a:
                                a.idatm_type = None
        else:
            for r in scheme.keys():
                if res_check and not res_check(r, scheme[r]):
                    del scheme[r]
        schemes[scheme_type] = scheme
    # create dictionary keyed on histidine residue with value of another
    # dictionary keyed on the nitrogen atoms with boolean values: True
    # equals should be protonated
    his_Ns = {}
    for r, protonation in schemes["his"].items():
        delta = r.find_atom("ND1")
        epsilon = r.find_atom("NE2")
        if delta is None or epsilon is None:
            # find the ring, etc.
            rings = r.structure.rings()
            for ring in rings:
                if r in rings.atoms.residues:
                    break
            else:
                continue
            # find CG by locating CB-CG bond
            ring_bonds = ring.bonds
            for ra in ring.atoms:
                if ra.element.name != "C":
                    continue
                for ba, b in zip(ra.neighbors, ra.bonds):
                    if ba.element.name == "C" and b not in ring_bonds:
                        break
                else:
                    continue
                break
            else:
                continue
            nitrogens = [a for a in ring.atoms if a.element.name == "N"]
            if len(nitrogens) != 2:
                continue
            if ra in nitrogens[0].neighbors:
                delta, epsilon = nitrogens
            else:
                epsilon, delta = nitrogens
        if protonation == "HID":
            his_Ns.update({delta: True, epsilon: False})
        elif protonation == "HIE":
            his_Ns.update({delta: False, epsilon: True})
        elif protonation == "HIP":
            his_Ns.update({delta: True, epsilon: True})
        else:
            continue
    for n, do_prot in his_Ns.items():
        if do_prot:
            type_info_for_atom[n] = type_info["Npl"]
            n.idatm_type = idatm_type[n] = "Npl"
        else:
            type_info_for_atom[n] = type_info["N2"]
            n.idatm_type = idatm_type[n] = "N2"

    for r, protonation in schemes["asp"].items():
        _handle_acid_protonation_scheme_item(r, protonation, asp_res_names,
                                             asp_prot_names, type_info,
                                             type_info_for_atom)

    for r, protonation in schemes["glu"].items():
        _handle_acid_protonation_scheme_item(r, protonation, glu_res_names,
                                             glu_prot_names, type_info,
                                             type_info_for_atom)

    for r, protonation in schemes["lys"].items():
        nz = r.find_atom("NZ")
        if protonation == "LYS":
            it = 'N3+'
        else:
            it = 'N3'
        ti = type_info[it]
        if nz is not None:
            type_info_for_atom[nz] = ti
            # avoid explicitly setting type if possible
            if nz.idatm_type != it:
                nz.idatm_type = it

    for r, protonation in schemes["cys"].items():
        sg = r.find_atom("SG")
        if protonation == "CYS":
            it = 'S3'
        else:
            it = 'S3-'
        ti = type_info[it]
        if sg is not None:
            type_info_for_atom[sg] = ti
            # avoid explicitly setting type if possible
            if sg.idatm_type != it:
                sg.idatm_type = it

    return atoms, type_info_for_atom, naming_schemas, idatm_type, \
            hydrogen_totals, his_Ns, coordinations, fake_N, fake_C
Exemple #8
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
Exemple #9
0
def find_incorrect_residues(session, model, heavy_atoms_only=True):
    from chimerax.atomic import Residue, TmplResidue
    from chimerax import mmcif
    residues = model.residues
    questionable = []
    for r in residues:
        pt = r.polymer_type
        tmpl = None
        if pt in (Residue.PT_AMINO, Residue.PT_NUCLEIC):
            start = True
            end = True
            if pt == Residue.PT_AMINO:
                fa = r.find_atom('N')
                la = r.find_atom('C')
            else:
                fa = r.find_atom('P')
                la = r.find_atom("O3'")
            if fa is not None:
                for fn in fa.neighbors:
                    if fn.residue != r:
                        start = False
                        break
            if la is not None:
                for ln in la.neighbors:
                    if ln.residue != r:
                        end = False
                        break
            try:
                tmpl = TmplResidue.get_template(r.name, start=start, end=end)
            except ValueError:
                tmpl = None
        if tmpl is None:
            if r.name not in _fetched_templates:
                session.logger.info(
                    'Fetching CCD definition for residue {} {}{}'.format(
                        r.name, r.chain_id, r.number))
            try:
                tmpl = mmcif.find_template_residue(session, r.name)
                _fetched_templates.add(r.name)
            except ValueError:
                session.logger.warning(
                    'Template {} not found in the Chemical Components Dictionary'
                    .format(r.name))
                continue
        if heavy_atoms_only:
            ra_names = set(r.atoms[r.atoms.element_names != 'H'].names)
            ta_names = set(
                [a.name for a in tmpl.atoms if a.element.name != 'H'])
        else:
            ra_names = set(r.atoms.names)
            ta_names = set([a.name for a in tmpl.atoms])
        ra_residuals = ra_names.difference(ta_names)
        ta_residuals = ta_names.difference(ra_names)
        if len(ta_residuals):
            if end:
                if pt == Residue.PT_AMINO:
                    print(
                        'C-terminal residue {} {}{}; ra_residuals: {}; ta_residuals: {}'
                        .format(r.name, r.chain_id, r.number, ra_residuals,
                                ta_residuals))
                if pt == Residue.PT_AMINO and not len(
                        ra_residuals) and ta_residuals == set(('OXT', )):
                    # Dangling C-terminal peptide. Allow.
                    continue
                elif pt == Residue.PT_NUCLEIC and not heavy_atoms_only and not len(
                        ra_residuals) and ta_residuals == set(("HO5'", )):
                    # Dangling 5' end. Allow
                    continue
            if start and not heavy_atoms_only:
                if pt == Residue.PT_AMINO and ta_residuals == set(
                    ('H', )) and ra_residuals == set(('H1', 'H2', 'H3')):
                    # Dangling N-terminal peptide. Allow
                    continue
                elif pt == Residue.PT_NUCLEIC and ta_residuals == set(
                    ("HO3'", )):
                    # Dangling 3' end. Allow.
                    continue

            questionable.append(r)
    return questionable
Exemple #10
0
def place_ligand(session,
                 ligand_id,
                 model=None,
                 position=None,
                 bfactor=None,
                 chain=None,
                 distance_cutoff=8.0,
                 sim_settle=False,
                 use_md_template=True,
                 md_template_name=None):
    '''
    Place a ligand at the given position, or the current centre of
    rotation if no :attr:`position` is specified. If the :attr:`bfactor` or
    :attr:`chain` arguments are not specified, the water will be assigned the
    missing properties based on the nearest residue within
    :attr:`distance_cutoff` of :attr:`position`, with the assigned B-factor
    being (average B-factor of nearest residue)+5. If :attr:`sim_settle` is
    True, a small local simulation will automatically start to settle the
    ligand into position.

    For residues of more than three atoms, if you have loaded an MD template for
    the residue (with matching residue name), any mis-matches between atoms in
    the CIF and MD templates will be automatically corrected as long as the
    changes only involve removing atoms from the CIF template and/or adding
    atoms that are directly connected to a single atom in the CIF template. The
    most common scenario where this arises is where the protonation state of the
    CIF template is different from that of the MD template.

    Note that at this stage no attempt is made at a preliminary fit to the
    density, or to avoid clashes: the ligand is simply placed at the given
    position with coordinates defined by the template in the Chemical
    Components Dictionary. Except for small, rigid ligands the use of
    :attr:`sim_settle`=True is inadvisable. If the ligand as placed clashes
    badly with the existing model, the best approach is to run the command
    `isolde ignore ~selAtoms` then start a simulation (which will involve *only*
    the new residue) to pull it into position (you may want to use pins where
    necessary). Once satisfied that there are no severe clashes, stop the
    simulation and run `isolde ~ignore` to reinstate all atoms for simulation
    purposes, and continue with your model building.
    '''
    from chimerax.geometry import find_closest_points
    from chimerax import mmcif
    from chimerax.core.errors import UserError
    if hasattr(session, 'isolde') and session.isolde.simulation_running:
        raise UserError('Cannot add atoms when a simulation is running!')
    if model is None:
        if hasattr(session,
                   'isolde') and session.isolde.selected_model is not None:
            model = session.isolde.selected_model
        else:
            raise UserError(
                'If model is not specified, ISOLDE must be started '
                'with a model loaded')
    if position is None:
        position = session.view.center_of_rotation
    matoms = model.atoms
    if bfactor is None or chain is None:
        _, _, i = find_closest_points([position], matoms.coords,
                                      distance_cutoff)
        if not len(i):
            err_str = (
                'No existing atoms found within the given distance cutoff '
                'of the target position. You may repeat with a larger cutoff or '
                'explicitly specify the B-factor and chain ID')
            if ligand_id == 'HOH':
                err_str += (
                    ', but keep in mind that placing waters outside of '
                    'H-bonding distance to the model is generally inadvisable.'
                )
            else:
                err_str += '.'
            raise UserError(err_str)
        na = matoms[i[0]]
        if chain is None:
            chain = na.residue.chain_id
        if bfactor is None:
            bfactor = na.residue.atoms.bfactors.mean() + 5
    tmpl = mmcif.find_template_residue(session, ligand_id)
    r = new_residue_from_template(model,
                                  tmpl,
                                  chain,
                                  position,
                                  b_factor=bfactor)
    if use_md_template and len(r.atoms) > 3:
        ff = session.isolde.forcefield_mgr[
            session.isolde.sim_params.forcefield]
        if md_template_name is None:
            from chimerax.isolde.openmm.amberff.template_utils import ccd_to_known_template
            md_template_name = ccd_to_known_template.get(ligand_id, None)
        if md_template_name is None:
            ligand_db = session.isolde.forcefield_mgr.ligand_db(
                session.isolde.sim_params.forcefield)
            from chimerax.isolde.openmm.openmm_interface import find_residue_templates
            from chimerax.atomic import Residues
            tdict = find_residue_templates(Residues([r]),
                                           ff,
                                           ligand_db=ligand_db,
                                           logger=session.logger)
            md_template_name = tdict.get(0)
        md_template = None
        if md_template_name is not None:
            md_template = ff._templates.get(md_template_name, None)

        if md_template is None:
            session.logger.warning(
                'place_ligand() was called with use_md_template=True, '
                'but no suitable template was found. This command has been ignored.'
            )
            return
        from ..template_utils import fix_residue_to_match_md_template
        fix_residue_to_match_md_template(session,
                                         r,
                                         md_template,
                                         cif_template=tmpl)

    matoms.selected = False
    r.atoms.selected = True
    if sim_settle:
        if not hasattr(session, 'isolde'):
            session.logger.warning(
                'ISOLDE is not running. sim_settle argument ignored.')
        elif model != session.isolde.selected_model:
            session.logger.warning(
                "New ligand was not added to ISOLDE's "
                "selected model. sim_settle argument ignored.")
        else:
            # defer the simulation starting until after the new atoms have been
            # drawn, to make sure their styling "sticks"
            def do_run(*_, session=session):
                from chimerax.core.commands import run
                run(session, 'isolde sim start sel')
                from chimerax.core.triggerset import DEREGISTER
                return DEREGISTER

            session.triggers.add_handler('frame drawn', do_run)
    return r