Пример #1
0
def _select_sequence(objects, sequence):
    from chimerax.atomic import Residues, Residue
    sel_residues = set()
    base_search_string = sequence.upper()
    protein_search_string = base_search_string.replace('B', '[DN]').replace('Z', '[EQ]')
    nucleic_search_string = base_search_string.replace('R', '[AG]').replace('Y', '[CTU]').replace(
        'N', '[ACGTU]')
    orig_res = objects.residues
    for chain in orig_res.chains.unique():
        search_string = protein_search_string \
            if chain.polymer_type == Residue.PT_PROTEIN else nucleic_search_string
        try:
            ranges = chain.search(search_string, case_sensitive=True)
        except ValueError as e:
            from chimerax.core.errors import UserError
            raise UserError(e)
        for start, length in ranges:
            sel_residues.update([r for r in chain.residues[start:start+length] if r])
    residues = Residues(sel_residues)
    atoms = residues.intersect(orig_res).atoms
    from chimerax.core.objects import Objects
    fobj = Objects(atoms = atoms, bonds = atoms.intra_bonds,
                   pseudobonds = atoms.intra_pseudobonds,
                   models = atoms.structures.unique())
    return fobj
Пример #2
0
 def restore_snapshot(cls, session, data):
     if data['version'] != 1:
         raise RestoreError("unknown nucleotide session state version")
     nuc = _nucleotides(session)
     infos = data['infos']
     for mol, (info, params) in infos.items():
         if info is None:
             if not hasattr(mol, '_nucleotide_info'):
                 continue
             residues = Residues(mol._nucleotide_info.keys())
             residues.atoms.clear_hide_bits(HIDE_NUCLEOTIDE)
             _remove_nuc_drawing(nuc, mol)
             continue
         if not hasattr(mol, '_nucleotide_info'):
             prev_residues = None
         else:
             prev_residues = Residues(mol._nucleotide_info.keys())
         nuc.structures.add(mol)
         nuc.need_rebuild.add(mol)
         _make_nuc_drawing(nuc, mol)
         if prev_residues is not None:
             mol._nucleotide_info.clear()
         mol._nucleotide_info.update(info)
         if prev_residues is not None:
             new_residues = Residues(info.keys())
             removed_residues = prev_residues - new_residues
             removed_residues.atoms.clear_hide_bits(HIDE_NUCLEOTIDE)
         mol._ladder_params.update(params)
     return nuc
Пример #3
0
def sort_bound_and_unbound_ligands(residues):
    unbound = []
    from collections import defaultdict
    bound_map = defaultdict(set)
    for r in residues:
        if not len(r.neighbors):
            unbound.append(r)
        cid = bound_to_polymer(r)
        if cid is not None:
            bound_map[cid].add(r)
        else:
            unbound.append(r)
    from chimerax.atomic import Residues
    return (Residues(unbound),
            {cid: Residues(rset)
             for cid, rset in bound_map.items()})
Пример #4
0
def sort_glycan_residues(residues):
    from chimerax.atomic import Residues
    polymer_stem_res = linked_polymer_residue(residues)
    for r in polymer_stem_res.neighbors:
        if r in residues:
            break
    order = [r]

    def _sort_walk(r, residues, order):
        bonds = [
            r.bonds_between(n)[0] for n in r.neighbors
            if n in residues and n not in order
        ]
        ordered_bonds = []
        for b in bonds:
            atoms = b.atoms
            if atoms[0].residue == r:
                ordered_bonds.append(atoms)
            else:
                ordered_bonds.append(atoms[::-1])
        ordered_bonds = list(
            sorted(ordered_bonds, key=lambda b: (int(b[0].name[-1]))))
        for b in ordered_bonds:
            next_r = b[1].residue
            order.append(next_r)
            _sort_walk(next_r, residues, order)

    _sort_walk(r, residues, order)
    return Residues(order)
Пример #5
0
def check_if_params_exist(session, residue):
    from chimerax.atomic import Residues
    from chimerax.core.errors import UserError
    from chimerax.isolde.openmm.openmm_interface import (
        create_openmm_topology,
        find_residue_templates,
    )
    ffmgr = session.isolde.forcefield_mgr
    ff_name = session.isolde.sim_params.forcefield
    ff = ffmgr[ff_name]
    #ligand_db = ffmgr.ligand_db(ff_name)
    template_dict = find_residue_templates(Residues([residue]),
                                           ff,
                                           logger=session.logger)
    err_str_base = (
        'Residue name {} already maps to MD template {}. If this is a '
        'different chemical species, you will need to choose a new name.')
    if len(template_dict):
        err_str = err_str_base.format(residue.name, template_dict[0])
        raise UserError(err_str)
    top, residue_templates = create_openmm_topology(residue.atoms, {})
    for r in top.residues():
        break
    assigned, ambiguous, unmatched = ff.assignTemplates(
        top, ignoreExternalBonds=False)
    if len(assigned):
        err_str = err_str_base.format(residue.name, assigned[r][0].name)
        raise UserError(err_str)
Пример #6
0
def sidechain_buried_score(residue):
    '''
    Defines how "buried" a sidechain is by counting the number of heavy atoms
    from other residues coming within 4A of any heavy atom from the sidechain.
    The returned score is a value ranging from 0 to 1, where 0 indicates no
    contact with other atoms, and 1 indicates 3 or more other atoms per
    sidechain atoms. The score scales linearly with the number of contacting
    atoms in between these values.
    '''
    from chimerax.geometry import find_close_points
    from chimerax.atomic import Residues
    import numpy
    r = residue
    m = r.structure
    other_residues = m.residues.subtract(Residues([r]))
    sidechain_atoms = r.atoms[numpy.logical_not(
        numpy.in1d(r.atoms.names, ['N', 'C', 'CA', 'O']))]
    if not len(sidechain_atoms):
        return 0
    other_atoms = other_residues.atoms
    cp = find_close_points(sidechain_atoms.coords, other_atoms.coords, 4.0)[1]
    score = (len(cp) / len(sidechain_atoms)) / 3
    if score > 1:
        score = 1
    return score
Пример #7
0
def cluster_unbound_ligands(model, unbound, cutoff=5):
    from chimerax.geometry import find_close_points
    from chimerax.atomic import Residue, Residues
    from collections import defaultdict
    import numpy
    m = model
    chain_ids = m.residues.unique_chain_ids
    other_residues = m.residues.subtract(unbound)
    #polymeric = m.residues[m.residues.polymer_types!=Residue.PT_NONE]
    ligand_atoms = unbound.atoms[unbound.atoms.element_names != 'H']
    chain_map = {}
    for cid in chain_ids:
        cres = other_residues[other_residues.chain_ids == cid]
        catoms = cres.atoms[cres.atoms.element_names != 'H']
        ci, li = find_close_points(catoms.coords, ligand_atoms.coords, cutoff)
        close_ligand_atoms = ligand_atoms[li]
        weights = numpy.ones(len(close_ligand_atoms), numpy.double)
        weights[close_ligand_atoms.element_names == 'C'] = _carbon_weight
        weights[close_ligand_atoms.elements.is_metal] = _metal_weight
        chain_map[cid] = Weighted_Counter(
            [a.residue for a in close_ligand_atoms], weights)
    unclassified = []
    closest_chain_map = defaultdict(list)
    for r in unbound:
        max_atoms = 0
        closest = None
        for cid in chain_ids:
            close = chain_map[cid].get(r, None)
            if close is not None:
                if close > max_atoms:
                    closest = cid
                    max_atoms = close
        if closest is not None:
            closest_chain_map[closest].append(r)
        else:
            unclassified.append(r)
    return {
        cid: Residues(residues)
        for cid, residues in closest_chain_map.items()
    }, Residues(unclassified)
Пример #8
0
def ncs_average_map(session, asu_map, full_map, reference_chain, ncs_chains):
    from chimerax.match_maker.match import (
        defaults, align
        )
    grid_points = asu_map.grid_points(asu_map.model_transform().inverse())
    from chimerax.atomic import Residues, Atoms
    interpolated_maps = []
    interpolated_maps.append(asu_map.data.matrix())
    original_map_position = full_map.position
    dssp_cache={}
    for ncs in ncs_chains:
        score, s1, s2 = align(session, ncs, reference_chain, defaults['matrix'],
            'nw', defaults['gap_open'], defaults['gap_extend'], dssp_cache)
        ref_residues = []
        ncs_residues = []
        for i, (mr, rr) in enumerate(zip(s1, s2)):
            if mr=='.' or rr=='.':
                continue
            ref_res = s1.residues[s1.gapped_to_ungapped(i)]
            match_res = s2.residues[s2.gapped_to_ungapped(i)]
            if not ref_res or not match_res:
                continue
            ref_residues.append(ref_res)
            ncs_residues.append(match_res)
        ref_residues = Residues(ref_residues)
        ncs_residues = Residues(ncs_residues)

        (ref_pa, ncs_pa) = paired_principal_atoms([ref_residues, ncs_residues])

        from chimerax.geometry import align_points
        tf, rms = align_points(ncs_pa.coords, ref_pa.coords)
        #full_map.position = tf * full_map.position
        from chimerax.core.commands import run
        #run(session, "fitmap #{} in #{}".format(full_map.id_string, asu_map.id_string))
        interpolated_maps.append(
            full_map.interpolated_values(grid_points, point_xform=tf.inverse()).reshape(asu_map.data.size))
        #full_map.position = original_map_position

    asu_map.data.matrix()[:] = sum(interpolated_maps)/len(interpolated_maps)
    asu_map.data.values_changed()
Пример #9
0
def parameterise_ligand(session,
                        residue,
                        net_charge=None,
                        charge_method='am1-bcc'):
    from chimerax.core.errors import UserError
    if len(residue.neighbors) != 0:
        raise UserError(
            f"Residue is covalently bound to {len(residue.neighbors)} other residues. "
            "ISOLDE currenly only supports parameterising new non-covalent ligands."
        )
    import numpy
    element_check = numpy.isin(residue.atoms.element_names,
                               _supported_elements)
    if not numpy.all(element_check):
        unsupported = residue.atoms[numpy.logical_not(element_check)]
        raise UserError(
            'Automatic ligand parameterisation currently only supports the following elements:\n'
            f'{", ".join(_supported_elements)}\n'
            f'Residue type {residue.name} contains the unsupported elements {", ".join(unsupported.element_names.unique())}.'
            'If you wish to work with this residue you will need to parameterise it using an external package.'
        )
    import os
    base_path = os.path.dirname(os.path.abspath(__file__))
    gaff2_parms = os.path.join(base_path, 'gaff2.dat')
    from chimerax.add_charge.charge import nonstd_charge, estimate_net_charge
    from chimerax.atomic import Residues
    if net_charge is None:
        # Workaround - trigger recalculation of idatm_types if necessary to make sure rings are current
        atom_types = residue.atoms.idatm_types
        net_charge = estimate_net_charge(residue.atoms)
    from chimerax.amber_info import amber_bin
    import tempfile
    with tempfile.TemporaryDirectory() as tempdir:
        nonstd_charge(session,
                      Residues([residue]),
                      net_charge,
                      charge_method,
                      temp_dir=tempdir)
        ante_out = os.path.join(tempdir, "ante.out.mol2")
        parmchk2 = os.path.join(amber_bin, 'parmchk2')
        frcmod_file = os.path.join(tempdir, "residue.frcmod")
        import subprocess
        subprocess.check_output([
            parmchk2, '-s', '2', '-i', ante_out, '-f', 'mol2', '-p',
            gaff2_parms, '-o', frcmod_file
        ])
        from .amber_convert import amber_to_ffxml
        amber_to_ffxml(frcmod_file,
                       ante_out,
                       output_name=residue.name + '.xml')
Пример #10
0
def parameterise_cmd(session,
                     residues,
                     override=False,
                     net_charge=None,
                     always_raise_errors=True):
    from chimerax.core.errors import UserError
    unique_residue_types = [
        residues[residues.names == name][0] for name in residues.unique_names
    ]
    for residue in unique_residue_types:
        if hasattr(session, 'isolde'):
            ff_name = session.isolde.sim_params.forcefield
            forcefield = session.isolde.forcefield_mgr[ff_name]
            ligand_db = session.isolde.forcefield_mgr.ligand_db(ff_name)
            from chimerax.isolde.openmm.openmm_interface import find_residue_templates
            from chimerax.atomic import Residues
            templates = find_residue_templates(Residues([residue]),
                                               forcefield,
                                               ligand_db=ligand_db,
                                               logger=session.logger)
            if len(templates):
                if not override:
                    raise UserError(
                        f'Residue name {residue.name} already corresponds to template {templates[0]} in '
                        f'the {ff_name} forcefield. If you wish to replace that template, re-run this '
                        'command with override=True')
        try:
            parameterise_ligand(session, residue, net_charge=net_charge)
        except Exception as e:
            if always_raise_errors:
                raise UserError(str(e))
            else:
                session.logger.warning(
                    f'Parameterisation of {residue.name} failed with the following message:'
                )
                session.logger.warning(str(e))
                continue
        session.logger.info(
            f'OpenMM ffXML file {residue.name} written to the current working directory.'
        )
        if hasattr(session, 'isolde'):
            if len(templates):
                forcefield._templates.pop(templates[0])
            forcefield.loadFile(f'{residue.name}.xml', resname_prefix='USER_')
            session.logger.info(
                f'New template added to forcefield as USER_{residue.name}. This ligand should '
                'now work in all remaining simulations for this session. To use in '
                'future sessions, load the ffXML file with ISOLDE\'s Load Residue MD Definition(s) button.'
            )
Пример #11
0
def set_normal(residues):
    molecules = residues.unique_structures
    nuc = _nucleotides(molecules[0].session)
    rds = {}
    for mol in molecules:
        _make_nuc_drawing(nuc, mol)
        rds[mol] = mol._nucleotide_info
    changed = {}
    for r in residues:
        if rds[r.structure].pop(r, None) is not None:
            changed.setdefault(r.structure, []).append(r)
    nuc.need_rebuild.update(changed.keys())
    import itertools
    Residues(itertools.chain(
        *changed.values())).atoms.clear_hide_bits(HIDE_NUCLEOTIDE)
Пример #12
0
def run_script(session):
    from chimerax.atomic import selected_residues, selected_bonds, Residues
    sel = selected_residues(session)
    if len(sel) != 2 or not all(sel.names=='CYS'):
        b = selected_bonds(session)
        if len(b) == 1:
            b = b[0]
            sel = Residues([a.residue for a in b.atoms])
    if len(sel) != 2 or not all(sel.names=='CYS'):
        from chimerax.core.errors import UserError
        raise UserError('Please select exactly two cysteine residues!')

    from chimerax.isolde.atomic.building.build_utils import break_disulfide
    break_disulfide(*sel)
    session.logger.info('Broke disulphide bond between {}.'.format(
    ' and '.join(['{}{}{}'.format(c.chain_id, c.number, c.insertion_code) for c in sel])
    ))
Пример #13
0
def sequence_align_all_residues(session, residues_a, residues_b):
    '''
    Peform a sequence alignment on each pair of chains, and concatenate the
    aligned residues into a single "super-alignment" (a pair of Residues
    objects with 1:1 correspondence between residues at each index). Each
    :class:`Residues` object in the arguments should contain residues from a
    single chain, and the chains in :param:`residues_a` and `residues_b` should
    be matched.

    Arguments:

        * session:
            - the ChimeraX session object
        * residues_a:
            - a list of ChimeraX :class:`Residues` objects
        * residues_b:
            - a list of ChimeraX :class:`Residues` objects
    '''
    from chimerax.match_maker.match import defaults, align, check_domain_matching
    alignments = ([], [])
    dssp_cache = {}
    for ra, rb in zip(residues_a, residues_b):
        uca = ra.unique_chains
        ucb = rb.unique_chains
        if not (len(uca) == 1 and len(ucb) == 1):
            raise TypeError(
                'Each residue selection must be from a single chain!')
        match, ref = [
            check_domain_matching([ch], dr)[0]
            for ch, dr in ((uca[0], ra), (ucb[0], rb))
        ]
        score, s1, s2 = align(session, match, ref, defaults['matrix'], 'nw',
                              defaults['gap_open'], defaults['gap_extend'],
                              dssp_cache)
        for i, (rr, mr) in enumerate(zip(s1, s2)):
            if mr == '.' or rr == '.':
                continue
            ref_res = s1.residues[s1.gapped_to_ungapped(i)]
            match_res = s2.residues[s2.gapped_to_ungapped(i)]
            if not ref_res or not match_res:
                continue
            alignments[0].append(ref_res)
            alignments[1].append(match_res)
    from chimerax.atomic import Residues
    return [Residues(a) for a in alignments]
Пример #14
0
 def get_protein_backbone_problems(structure, outliers_only=True):
     from chimerax.isolde import session_extensions as sx
     rmgr = sx.get_ramachandran_mgr(structure.session)
     residues = structure.residues
     if outliers_only:
         f = rmgr.outliers
     else:
         f = rmgr.non_favored
     problems = f(residues)
     # Also count twisted and cis-nonPro peptide bonds here
     cis = rmgr.cis(residues)
     cis_nonpro = cis[cis.names != 'PRO']
     # Note: this will probably lead to double-counting since
     # twisted peptides will usually also be captured by
     # get_torsion_restraint_problems()... but ultimately I
     # don't think that's a huge problem.
     twisted = rmgr.twisted(residues)
     from chimerax.atomic import concatenate, Residues
     twisted = Residues([t[0] for t in twisted])
     problems = concatenate([problems, cis_nonpro, twisted])
     problem_ramas = rmgr.get_ramas(problems)
     return problem_ramas
Пример #15
0
def assign_bound_ligand_chain_ids_and_residue_numbers(m, bound_map):
    # The wwPDB steering committee has dictated that for protein-linked glycans,
    # the following rules apply:
    #   - if the modelled portion of the glycan is a single residue, it should have
    #     the same chain ID as the protein.
    #   - if more than one residue, it should have a unique chain ID.
    from chimerax.atomic import Residues, Residue, concatenate
    import numpy
    for cid, bound_residues in bound_map.items():
        first_ligand_number = next_available_ligand_number(m, cid)
        new_glycan_cid = []
        assign_to_chain = []
        groups = independent_groupings(bound_residues)
        for g in groups:
            if len(g) == 1:
                assign_to_chain.append(g)
            elif any(r.name in known_sugars for r in g):
                new_glycan_cid.append(g)
            else:
                assign_to_chain.append(g)
        new_glycan_cid = list(
            sorted(new_glycan_cid,
                   key=lambda g: linked_polymer_residue(g).number))
        assign_to_chain = list(
            sorted(assign_to_chain,
                   key=lambda g: linked_polymer_residue(g).number))
        for i, g in enumerate(new_glycan_cid):
            gid = cid + 'gl' + str(i)
            residues = sort_glycan_residues(g)
            residues.chain_ids = gid
            m.renumber_residues(residues, 1)
        if len(assign_to_chain):
            assign_to_chain = concatenate(
                [Residues(g) for g in assign_to_chain])
            assign_to_chain.chain_ids = 'XXtmp'
            m.renumber_residues(assign_to_chain, first_ligand_number)
            assign_to_chain.chain_ids = cid
Пример #16
0
def restrain_small_ligands(model,
                           distance_cutoff=4,
                           heavy_atom_limit=3,
                           spring_constant=5000,
                           bond_to_carbon=False):
    '''
    Residues with a small number of heavy atoms can be problematic in MDFF if
    unrestrained, since if knocked out of density they tend to simply keep
    going. It is best to restrain them with distance restraints to suitable
    surrounding atoms or, failing that, to their starting positions.

    Args:
        * model:
            - a :class:`chimerax.atomic.AtomicStructure` instance
        * distance_cutoff (default = 3.5):
            - radius in Angstroms to look for candidate heavy atoms for distance
              restraints. If no candidates are found, a position restraint will
              be applied instead.
        * heavy_atom_limit (default = 3):
            - Only residues with a number of heavy atoms less than or equal to
              `heavy_atom_limit` will be restrained
        * spring_constant (default = 500):
            - strength of each restraint, in :math:`kJ mol^{-1} nm^{-2}`
        * bond_to_carbon (default = `False`):
            - if `True`, only non-carbon heavy atoms will be restrained using
              distance restraints.
    '''
    from chimerax.atomic import Residue, Residues
    residues = model.residues
    ligands = residues[residues.polymer_types == Residue.PT_NONE]
    small_ligands = Residues([
        r for r in ligands
        if len(r.atoms[r.atoms.element_names != 'H']) < heavy_atom_limit
    ])
    from .. import session_extensions as sx
    drm = sx.get_distance_restraint_mgr(model)
    prm = sx.get_position_restraint_mgr(model)
    all_heavy_atoms = model.atoms[model.atoms.element_names != 'H']
    if not bond_to_carbon:
        all_heavy_atoms = all_heavy_atoms[all_heavy_atoms.element_names != 'C']
    all_heavy_coords = all_heavy_atoms.coords

    from chimerax.geometry import find_close_points, distance
    for r in small_ligands:
        r_heavy_atoms = r.atoms[r.atoms.element_names != 'H']
        if not bond_to_carbon:
            r_non_carbon_atoms = r_heavy_atoms[
                r_heavy_atoms.element_names != 'C']
            if not len(r_non_carbon_atoms):
                # No non-carbon heavy atoms. Apply position restraints
                prs = prm.get_restraints(r_heavy_atoms)
                prs.targets = prs.atoms.coords
                prs.spring_constants = spring_constant
                prs.enableds = True
                continue
            r_heavy_atoms = r_non_carbon_atoms
        r_indices = all_heavy_atoms.indices(r_heavy_atoms)
        r_coords = r_heavy_atoms.coords
        applied_drs = False
        for ra, ri, rc in zip(r_heavy_atoms, r_indices, r_coords):
            _, found_i = find_close_points([rc], all_heavy_coords,
                                           distance_cutoff)
            found_i = found_i[found_i != ri]
            num_drs = 0
            for fi in found_i:
                if fi in r_indices:
                    continue
                dr = drm.add_restraint(ra, all_heavy_atoms[fi])
                dr.spring_constant = spring_constant
                dr.target = distance(rc, all_heavy_coords[fi])
                dr.enabled = True
                num_drs += 1
                # applied_drs = True
        if num_drs < 3:
            # Really need at least 3 distance restraints (probably 4, actually,
            # but we don't want to be *too* restrictive) to be stable in 3D
            # space. If we have fewer than that, add position restraints to be
            # sure.
            prs = prm.add_restraints(r_heavy_atoms)
            prs.targets = prs.atoms.coords
            prs.spring_constants = spring_constant
            prs.enableds = True
Пример #17
0
    def __init__(self, session, isolde, atoms):
        '''
        Initialise the object, including creating the splines from the current
        coordinates. No restraints are applied at this stage.

        Args:
            * session:
                - the ChimeraX master session object
            * isolde:
                - the :class:`Isolde` session
            * atoms:
                - a :class:`chimerax.AtomicStructure` instance. All atoms must
                  be mobile, and from a single contiguous stretch of peptide
                  chain. All unique residues in the selection will be chosen for
                  shifting
        '''
        if not is_continuous_protein_chain(atoms):
            raise TypeError(
                'Selection must be atoms from a continuous peptide chain!')

        self.spring_constant =\
           isolde.sim_params.position_restraint_spring_constant.value_in_unit(
               defaults.OPENMM_SPRING_UNIT
           ) # kJ/mol/A2

        # Number of GUI update steps between each residue along the spline
        self.spline_steps_per_residue = 10
        self._spline_step = 1 / self.spline_steps_per_residue
        # Current value of the spline parameter
        self._current_position_on_spline = 0

        from chimerax.core.triggerset import TriggerSet
        triggers = self.triggers = TriggerSet()
        for t in (
                'register shift started',
                'register shift finished',
                'register shift released',
        ):
            triggers.add_trigger(t)

        self.finished = False

        # Trigger handler to update position along spline
        self._handler = None

        self.session = session
        self.isolde = isolde
        from .. import session_extensions as sx
        self._pr_mgr = sx.get_position_restraint_mgr(isolde.selected_model)
        self.polymer = find_polymer(atoms)
        residues = atoms.unique_residues
        from chimerax.atomic import Residues
        residues = self.residues = Residues(
            sorted(residues,
                   key=lambda r: (r.chain_id, r.number, r.insertion_code)))
        self._extended_atoms = None

        nres = len(residues)
        # We need to build up the array of atoms procedurally here, since
        # we want to know where there's a gap in the CB positions.
        atoms = self._make_atom_arrays(residues)
        coords = []
        n_atoms = self._n_atoms = Atoms(atoms[:, 0])
        ncoords = n_atoms.coords

        ca_atoms = self._ca_atoms = Atoms(atoms[:, 1])
        cacoords = ca_atoms.coords

        c_atoms = self._c_atoms = Atoms(atoms[:, 2])
        ccoords = c_atoms.coords

        nocb = numpy.equal(atoms[:, 3], None)
        nocb_indices = numpy.argwhere(nocb).ravel()
        cb_indices = self._cb_indices = numpy.argwhere(
            numpy.invert(nocb)).ravel()
        cbcoords = numpy.empty([nres, 3])
        cb_atoms = self._cb_atoms = Atoms(atoms[cb_indices, 3])
        cbcoords[cb_indices] = cb_atoms.coords

        # Fill in the missing CB positions. If CB is missing we'll assume
        # we're dealing with a glycine and fudge it by using the HA3
        # position

        glyres = residues[nocb_indices]
        glyha3 = glyres.atoms.filter(glyres.atoms.names == 'HA3')
        cbcoords[nocb_indices] = glyha3.coords

        u_vals = numpy.arange(0, nres)

        # Prepare the splines
        nspl = self.n_spline = interpolate.splprep(ncoords.transpose(),
                                                   u=u_vals)
        caspl = self._ca_spline = interpolate.splprep(cacoords.transpose(),
                                                      u=u_vals)
        cspl = self._c_spline = interpolate.splprep(ccoords.transpose(),
                                                    u=u_vals)
        cbspl = self._cb_spline = interpolate.splprep(cbcoords.transpose(),
                                                      u=u_vals)
Пример #18
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
Пример #19
0
    def interpolate(self, m, res_interp, color_segments=False):
        """Interpolate to new conformation 'm'."""

        #
        # Find matching set of residues.  First try for
        # one-to-one residue match, and, if that fails,
        # then finding a common set of residues.
        #
        from . import segment
        sm = self.mol
        from time import time
        t0 = time()
        cf = self.core_fraction
        mhs = self.min_hinge_spacing
        log = self.log
        if self.match_same:
            results = segment.segmentHingeSame(sm, m, cf, mhs, log=log)
        else:
            from .segment import AtomPairingError
            try:
                results = segment.segmentHingeExact(sm, m, cf, mhs, log=log)
            except AtomPairingError:
                try:
                    results = segment.segmentHingeApproximate(sm,
                                                              m,
                                                              cf,
                                                              mhs,
                                                              log=log)
                except AtomPairingError as e:
                    from chimerax.core.errors import UserError
                    raise UserError(str(e))
        t1 = time()
        global ht
        ht += t1 - t0
        segments, atomMap = results
        from chimerax.atomic import Residues, Atoms
        res_groups = [Residues(r0) for r0, r1 in segments]
        if len(atomMap) < sm.num_atoms:
            paired_atoms = Atoms(tuple(atomMap.keys()))
            unpaired_atoms = sm.atoms.subtract(paired_atoms)
            unpaired_atoms.delete()

        if sm.deleted:
            from chimerax.core.errors import UserError
            raise UserError('No atoms matched')

        #
        # Interpolate between current conformation in trajectory
        # and new conformation
        #
        t0 = time()
        # Make coordinate set arrays for starting and final coordinates
        nc = sm.coordset_size
        matoms = sm.atoms
        maindices = matoms.coord_indices
        from numpy import float64, empty
        coords0 = empty((nc, 3), float64)
        coords0[maindices] = matoms.coords
        coords1 = empty((nc, 3), float64)
        # Convert to trajectory local coordinates.
        xform = sm.scene_position.inverse() * m.scene_position
        coords1[maindices] = xform * Atoms([atomMap[a] for a in matoms]).coords
        from .interpolate import SegmentInterpolator
        seg_interp = SegmentInterpolator(res_groups, self.method, coords0,
                                         coords1)

        from .interpolate import interpolate
        coordsets = interpolate(coords0, coords1, seg_interp, res_interp,
                                self.rate, self.frames, sm.session.logger)
        base_id = max(sm.coordset_ids) + 1
        for i, cs in enumerate(coordsets):
            sm.add_coordset(base_id + i, cs)
        sm.active_coordset_id = base_id + i
        t1 = time()
        global it
        it += t1 - t0

        return res_groups
Пример #20
0
def place_peptide(structure,
                  sequence,
                  phi_psis,
                  *,
                  position=None,
                  rot_lib=None,
                  chain_id=None):
    """
    Place a peptide sequence.

    *structure* is an AtomicStructure to add the peptide to.

    *sequence* contains the sequence as a string.

    *phi_psis* is a list of phi/psi tuples, one per residue.

    *position* is either an array or sequence specifying an xyz world coordinate position or None.
    If None, the peptide is positioned at the center of the view.

    *rot_lib* is the name of the rotamer library to use to position the side chains.
    session.rotamers.library_names lists installed rotamer library names.  If None, a default
    library will be used.

    *chain_id* is the desired chain ID for the peptide.  If None, earliest alphabetical chain ID
    not already in use in the structure will be used (upper case taking precedence over lower
    case).

    returns a Residues collection of the added residues
    """

    if not sequence:
        raise PeptideError("No sequence supplied")
    sequence = sequence.upper()
    if not sequence.isupper():
        raise PeptideError("Sequence contains non-alphabetic characters")
    from chimerax.atomic import Sequence
    for c in sequence:
        try:
            r3 = Sequence.protein1to3[c]
        except KeyError:
            raise PeptideError("Unrecognized protein 1-letter code: %s" % c)
        if r3[-1] == 'X':
            raise PeptideError(
                "Peptide sequence cannot contain ambiguity codes (i.e. '%s')" %
                c)

    if len(sequence) != len(phi_psis):
        raise PeptideError("Number of phi/psis not equal to sequence length")

    session = structure.session

    open_models = session.models[:]
    if len(open_models) == 0:
        need_focus = True
    elif len(open_models) == 1:
        if open_models[0] == structure:
            need_focus = not structure.atoms
        else:
            need_focus = False
    else:
        need_focus = False

    if position is None:
        position = session.main_view.center_of_rotation
    from numpy import array
    position = array(position)

    prev = [None] * 3
    pos = 1
    from chimerax.atomic.struct_edit import DIST_N_C, DIST_CA_N, DIST_C_CA, DIST_C_O, \
        find_pt, add_atom, add_dihedral_atom
    serial_number = None
    residues = []
    prev_psi = 0
    if chain_id is None:
        chain_id = unused_chain_id(structure)
    from numpy import array
    for c, phi_psi in zip(sequence, phi_psis):
        phi, psi = phi_psi
        while structure.find_residue(chain_id, pos):
            pos += 1
        r = structure.new_residue(Sequence.protein1to3[c], chain_id, pos)
        residues.append(r)
        for backbone, dist, angle, dihed in [('N', DIST_N_C, 116.6, prev_psi),
                                             ('CA', DIST_CA_N, 121.9, 180.0),
                                             ('C', DIST_C_CA, 110.1, phi)]:
            if prev[0] is None:
                pt = array([0.0, 0.0, 0.0])
            elif prev[1] is None:
                pt = array([dist, 0.0, 0.0])
            elif prev[2] is None:
                pt = find_pt(prev[0].coord, prev[1].coord,
                             array([0.0, 1.0, 0.0]), dist, angle, 0.0)
            else:
                pt = find_pt(prev[0].coord, prev[1].coord, prev[2].coord, dist,
                             angle, dihed)
            a = add_atom(backbone,
                         backbone[0],
                         r,
                         pt,
                         serial_number=serial_number,
                         bonded_to=prev[0])
            serial_number = a.serial_number + 1
            prev = [a] + prev[:2]
        o = add_dihedral_atom("O",
                              "O",
                              prev[0],
                              prev[1],
                              prev[2],
                              DIST_C_O,
                              120.4,
                              180 + psi,
                              bonded=True)
        prev_psi = psi
    # C terminus O/OXT at different angle than mainchain O
    structure.delete_atom(o)
    add_dihedral_atom("O",
                      "O",
                      prev[0],
                      prev[1],
                      prev[2],
                      DIST_C_O,
                      117.0,
                      180 + psi,
                      bonded=True)
    add_dihedral_atom("OXT",
                      "O",
                      prev[0],
                      prev[1],
                      prev[2],
                      DIST_C_O,
                      117.0,
                      psi,
                      bonded=True)

    from chimerax.atomic import Residues
    residues = Residues(residues)

    from chimerax.swap_res import swap_aa
    # swap_aa is capable of swapping all residues in one call, but need to process one by one
    # since side-chain clashes are only calculated against pre-existing side chains
    kw = {}
    if rot_lib:
        kw['rot_lib'] = rot_lib
    for r in residues:
        swap_aa(session, [r], "same", criteria="cp", log=False, **kw)

    # find peptide center
    atoms = residues.atoms
    coords = atoms.coords
    center = coords.mean(0)
    correction = position - center
    atoms.coords = coords - correction

    from chimerax.std_commands.dssp import compute_ss
    compute_ss(session, structure)

    if need_focus:
        from chimerax.core.commands import run
        run(session, "view")
    return residues
Пример #21
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
Пример #22
0
def assign_charges(session, uncharged_residues, his_scheme):
    from chimerax.atomic import Atom
    Atom.register_attr(session,
                       "charge",
                       "coulombic coloring",
                       attr_type=float)
    by_structure = {}
    for r in uncharged_residues:
        #if r.name == 'HIS':
        #    r._coulombic_his_scheme = his_scheme
        by_structure.setdefault(r.structure, []).append(r)

    missing_heavies = []
    extra_atoms = []
    copy_needed = {}
    for struct, residue_list in list(by_structure.items()):
        from chimerax.atomic import Residues
        by_structure[struct] = residues = Residues(residue_list)
        missing, extra = check_residues(residues)
        heavies = [info for info in missing if not info[1].startswith('H')]
        missing_heavies.extend(heavies)
        copy_needed[struct] = len(heavies) < len(missing)
        extra_atoms.extend(extra)

    if extra_atoms:
        from chimerax.core.commands import commas
        if len(extra_atoms) <= 7:
            atoms_text = commas([str(a) for a in extra_atoms],
                                conjunction="and")
        else:
            atoms_text = commas([str(a) for a in extra_atoms[:5]] +
                                ["%d other atoms" % (len(extra_atoms) - 5)],
                                conjunction="and")
        if len([a for a in extra_atoms
                if a.element.number == 1]) == len(extra_atoms):
            hint = "  Try deleting all hydrogens first."
        else:
            hint = ""
        raise ChargeError(
            "Atoms with non-standard names found in standard residues: %s.%s" %
            (atoms_text, hint))

    if missing_heavies:
        from chimerax.core.commands import commas
        if len(missing_heavies) <= 7:
            atoms_text = commas(
                [str(r) + ' ' + an for r, an in missing_heavies],
                conjunction="and")
        else:
            atoms_text = commas(
                [str(r) + ' ' + an for r, an in missing_heavies[:5]] +
                ["%d other atoms" % (len(missing_heavies) - 5)],
                conjunction="and")
        session.logger.warning(
            "The following heavy (non-hydrogen) atoms are missing, which may result"
            " in inaccurate electrostatics: %s" % atoms_text)

    for struct, struct_residues in by_structure.items():
        if copy_needed[struct]:
            session.logger.status("Copying %s" % struct, secondary=True)
            charged_struct = struct.copy(name="copy of " + struct.name)
            orig_a_to_copy = {}
            copy_a_to_orig = {}
            for o_a, c_a in zip(struct.atoms, charged_struct.atoms):
                orig_a_to_copy[o_a] = c_a
                copy_a_to_orig[c_a] = o_a
            orig_r_to_copy = {}
            copy_r_to_orig = {}
            for o_r, c_r in zip(struct.residues, charged_struct.residues):
                orig_r_to_copy[o_r] = c_r
                copy_r_to_orig[c_r] = o_r
            from chimerax.addh.cmd import cmd_addh
            hbond = False
            if his_scheme is None:
                if len(struct_residues[struct_residues.names == "HIS"]) > 0:
                    hbond = True
            from chimerax.atomic import AtomicStructures
            addh_structures = AtomicStructures([charged_struct])
            session.logger.status("Adding hydrogens to copy of %s" % struct,
                                  secondary=True)
            session.silent = True
            try:
                cmd_addh(session, addh_structures, hbond=hbond)
            finally:
                session.silent = False
            charged_residues = [orig_r_to_copy[r] for r in struct_residues]
            session.logger.status("Assigning charges to copy of %s" % struct,
                                  secondary=True)
        else:
            charged_struct = struct
            charged_residues = struct_residues

        # assign charges
        assign_residue_charges(charged_residues, his_scheme)

        if copy_needed[struct]:
            session.logger.status("Copying charges back to %s" % struct,
                                  secondary=True)
            for o_r in struct_residues:
                for o_a in o_r.atoms:
                    c_a = orig_a_to_copy[o_a]
                    for nb in c_a.neighbors:
                        if nb.residue == c_a.residue and nb not in copy_a_to_orig:
                            c_a.charge += nb.charge
                    o_a.charge = c_a.charge
            session.logger.status("Destroying copy of %s" % struct,
                                  secondary=True)
            charged_struct.delete()
Пример #23
0
    def _update_unparameterised_residues_list(self,
                                              *_,
                                              ff=None,
                                              ambiguous=None,
                                              unmatched=None,
                                              residues=None):
        table = self._residue_table
        tlist = self._template_list
        if not table.isVisible():
            return
        table.setRowCount(0)
        tlist.clear()
        if ambiguous is None and unmatched is None:
            if self.isolde.selected_model is None:
                return
            residues = self.isolde.selected_model.residues
            h = residues.atoms[residues.atoms.element_names == 'H']
            addh = False
            from ..dialog import choice_warning
            if not len(h):
                addh = choice_warning(
                    'This model does not appear to have hydrogens. Would you like to add them first?'
                )
            elif self.suspiciously_low_h(residues):
                addh = choice_warning(
                    'This model has significantly fewer hydrogens than expected for a natural molecule. Would you like to run AddH first?'
                )
            elif self.waters_without_h(residues):
                addh = choice_warning(
                    'Some or all waters are missing hydrogens. Would you like to add them first?'
                )
            if addh:
                from chimerax.core.commands import run
                run(self.session, 'addh')
            from chimerax.atomic import Residues
            residues = Residues(
                sorted(residues,
                       key=lambda r: (r.chain_id, r.number, r.insertion_code)))
            if ff is None:
                ffmgr = self.isolde.forcefield_mgr
                ff = ffmgr[self.isolde.sim_params.forcefield]
                ligand_db = ffmgr.ligand_db(self.isolde.sim_params.forcefield)
            from ..openmm.openmm_interface import find_residue_templates, create_openmm_topology
            template_dict = find_residue_templates(residues,
                                                   ff,
                                                   ligand_db=ligand_db,
                                                   logger=self.session.logger)
            top, residue_templates = create_openmm_topology(
                residues.atoms, template_dict)
            _, ambiguous, unmatched = ff.assignTemplates(
                top,
                ignoreExternalBonds=True,
                explicit_templates=residue_templates)
        from Qt.QtWidgets import QTableWidgetItem
        table.setRowCount(len(unmatched) + len(ambiguous))
        count = 0
        for r in unmatched:
            by_name, by_comp = ff.find_possible_templates(r)
            cx_res = residues[r.index]
            data = (
                cx_res.chain_id,
                cx_res.name + ' ' + str(cx_res.number),
            )
            for j, d in enumerate(data):
                item = QTableWidgetItem(d)
                item.setData(USER_ROLE, (cx_res, by_name, by_comp))
                table.setItem(count, j, item)
            count += 1
        for r, template_info in ambiguous.items():

            cx_res = residues[r.index]
            data = (cx_res.chain_id, cx_res.name + ' ' + str(cx_res.number),
                    ', '.join([ti[0].name for ti in template_info]))
            for j, d in enumerate(data):
                item = QTableWidgetItem(d)
                item.setData(USER_ROLE,
                             (cx_res, [], [[ti[0].name, 0]
                                           for ti in template_info]))
                table.setItem(count, j, item)
            count += 1
        table.resizeColumnsToContents()
Пример #24
0
def _rebuild_molecule(trigger_name, mol):
    if trigger_name == 'changes':
        mol, changes = mol
        # check changes for reasons we're interested in
        # ie., add/delete/moving atoms
        if changes.num_deleted_atoms():
            pass  # rebuild
        elif not set(changes.residue_reasons()).isdisjoint(_ResidueReasons):
            pass  # rebuild
        elif 'active_coordset changed' in changes.structure_reasons():
            pass  # rebuild
        else:
            reasons = set(changes.atom_reasons())
            if reasons.isdisjoint(_AtomReasons):
                # no reason to rebuild
                return
    mol.update_graphics_if_needed()  # need to recompute ribbon first
    nuc = _nucleotides(mol.session)
    nd = _make_nuc_drawing(nuc, mol, recreate=True)
    if nd is None:
        nuc.need_rebuild.discard(mol)
        return
    nuc_info = mol._nucleotide_info
    all_shapes = []
    # figure out which residues are of which type because
    # ladder needs knowledge about the whole structure
    sides = {}
    for k in SideOptions:
        sides[k] = []
    for r in tuple(nuc_info):
        if r.deleted:
            # Sometimes the residues are gone,
            # but there's a still reference to them.
            del nuc_info[r]
            continue
        sides[nuc_info[r]['side']].append(r)
    if not nuc_info:
        # no residues to track in structure
        _remove_nuc_drawing(nuc, mol)
        return
    all_residues = Residues(nuc_info.keys())
    # create shapes
    hide_riboses = []
    hide_bases = []
    show_glys = []
    residues = sides['ladder']
    if residues:
        residues = Residues(residues)
        # redo all ladder nodes
        # TODO: hide hydrogen bonds between matched bases
        shapes, hide_residues = make_ladder(nd, residues, mol._ladder_params)
        all_shapes.extend(shapes)
        hide_riboses.extend(hide_residues)
        hide_bases.extend(hide_residues)
    residues = sides['fill/slab'] + sides['slab']
    if residues:
        shapes, hide_residues = make_slab(nd, residues, nuc_info)
        all_shapes.extend(shapes)
        hide_bases.extend(hide_residues)
        show_glys.extend(hide_residues)
    residues = sides['tube/slab']
    if residues:
        shapes, hide_residues = make_slab(nd, residues, nuc_info)
        all_shapes.extend(shapes)
        hide_bases.extend(hide_residues)
        shapes, hide_residues, need_glys = make_tube(nd, hide_residues,
                                                     nuc_info)
        all_shapes.extend(shapes)
        hide_riboses.extend(hide_residues)
        show_glys.extend(need_glys)
    residues = sides['orient']
    if residues:
        for r in residues:
            shapes = draw_orientation(nd, r)
            all_shapes.extend(shapes)
    hide_riboses = Residues(hide_riboses)
    hide_bases = Residues(hide_bases)
    if all_shapes:
        nd.add_shapes(all_shapes)

    if hide_bases:
        # Until we have equivalent of ribbon_coord for atoms
        # hidden by nucleotide representations, we hide the
        # hydrogen bonds to atoms hidden by nucleotides.
        hide_hydrogen_bonds(hide_bases)

    # make sure ribose/base atoms are hidden/shown
    hide_all = hide_riboses & hide_bases
    hide_riboses = hide_riboses - hide_all
    hide_bases = hide_bases - hide_all

    all_residues.atoms.clear_hide_bits(HIDE_NUCLEOTIDE)

    set_hide_atoms(BaseAtomsRE, hide_bases)
    rib_res = hide_all.filter(hide_all.ribbon_displays)
    other_res = hide_all - rib_res
    set_hide_atoms(BaseRiboseAtomsRE, rib_res)
    set_hide_atoms(BaseRiboseAtomsNoRibRE, other_res)
    rib_res = hide_riboses.filter(hide_riboses.ribbon_displays)
    other_res = hide_riboses - rib_res
    set_hide_atoms(RiboseAtomsRE, rib_res)
    set_hide_atoms(RiboseAtomsNoRibRE, other_res)

    for residue in show_glys:
        rd = nuc_info[residue]
        tag = standard_bases[rd['name']]['tag']
        ba = residue.find_atom(anchor(BASE, tag))
        c1p = residue.find_atom("C1'")
        if c1p and ba:
            c1p.clear_hide_bits(HIDE_NUCLEOTIDE)
            ba.clear_hide_bits(HIDE_NUCLEOTIDE)

    # TODO: If a hidden atom is pseudobonded to another atom,
    # then hide the pseudobond.

    nuc.need_rebuild.discard(mol)
Пример #25
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)
Пример #26
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