Beispiel #1
0
def chain_spheres(atoms, session):
    if atoms is None:
        from chimerax.atomic import all_atoms
        atoms = all_atoms(session)
    if len(atoms) == 0:
        from chimerax.core.errors import UserError
        raise UserError('No atoms specified')
    from numpy import mean
    s = [SphereGroup('#%s/%s'%(m.id_string,cid), catoms)
         for m, cid, catoms in atoms.by_chain]
    return s
Beispiel #2
0
def shortcut_atoms(session):
    matoms = []
    sel = session.selection
    atoms_list = sel.items('atoms')
    from chimerax.atomic import concatenate, Atoms
    if atoms_list:
        atoms = concatenate(atoms_list)
    elif sel.empty():
        # Nothing selected, so operate on all atoms
        from chimerax.atomic import all_atoms
        atoms = all_atoms(session)
    else:
        atoms = Atoms()
    return atoms
Beispiel #3
0
def check_atoms(atoms, session):
    if atoms is None:
        from chimerax.atomic import all_atoms
        atoms = all_atoms(session)
        if len(atoms) == 0:
            from chimerax.core.errors import UserError
            raise UserError('No atomic models open.')
        atoms.spec = 'all atoms'
    elif len(atoms) == 0:
        msg = 'No atoms specified'
        if hasattr(atoms, 'spec'):
            msg += ' by %s' % atoms.spec
        from chimerax.core.errors import UserError
        raise UserError(msg)
    return atoms
Beispiel #4
0
def _surface_atoms(session, objects):
    if objects is None:
        from chimerax.atomic import all_atoms
        atoms = all_atoms(session)
    else:
        atoms = objects.atoms
        from chimerax.atomic import MolecularSurface
        satoms = [
            s.atoms for s in objects.models if isinstance(s, MolecularSurface)
        ]
        if satoms:
            from chimerax.atomic import concatenate, Atoms
            atoms = concatenate([atoms] + satoms,
                                Atoms,
                                remove_duplicates=True)
    return atoms
Beispiel #5
0
def delete_atoms(session, atoms, attached_hyds=True):
    '''Delete atoms.

    Parameters
    ----------
    atoms : Atoms collection or None (all atoms)
        Delete these atoms.  If all atoms of a model are closed then the model is closed.
    '''
    if atoms is None:
        from chimerax.atomic import all_atoms
        atoms = all_atoms(session)
    if attached_hyds:
        nbs = atoms.neighbors
        hyds = nbs.filter(nbs.elements.numbers == 1)
        atoms = atoms.merge(hyds)
    atoms.delete()
    from chimerax.atomic import StructureData
    session.models.close([
        s for s in session.models
        if isinstance(s, StructureData) and StructureData.deleted.fget(s)
    ])
Beispiel #6
0
def cmd_coulombic(session,
                  atoms,
                  *,
                  surfaces=None,
                  his_scheme=None,
                  offset=1.4,
                  spacing=1.0,
                  padding=5.0,
                  map=False,
                  palette=None,
                  range=None,
                  dist_dep=True,
                  dielectric=4.0):
    if map:
        session.logger.warning(
            "Computing electrostatic volume map not yet supported")
    session.logger.status("Computing Coulombic charge surface%s" %
                          ("/volume" if map else ""))
    if palette is None:
        from chimerax.core.colors import BuiltinColormaps
        cmap = BuiltinColormaps["red-white-blue"]
    if not cmap.values_specified:
        rmin, rmax = (-10.0, 10.0) if range is None else range
        cmap = cmap.linear_range(rmin, rmax)
    session.logger.status("Matching atoms to surfaces", secondary=True)
    atoms_per_surf = []
    from chimerax.atomic import all_atomic_structures, MolecularSurface, all_atoms
    if atoms is None:
        if surfaces is None:
            # surface all chains
            for struct in all_atomic_structures(session):
                # don't create surface until charges checked
                if struct.num_chains == 0:
                    atoms_per_surf.append((struct.atoms, None, None))
                else:
                    for chain in struct.chains:
                        atoms_per_surf.append(
                            (chain.existing_residues.atoms, None, None))
        else:
            for srf in surfaces:
                if isinstance(srf, MolecularSurface):
                    atoms_per_surf.append((srf.atoms, None, srf))
                else:
                    atoms_per_surf.append((all_atoms(session), None, srf))
    else:
        if surfaces is None:
            # on a per-structure basis, determine if the atoms contain any polymers, and if so then
            # surface those chains (and not non-polymers); otherwise surface the atoms
            by_chain = {}
            for struct, chain_id, chain_atoms in atoms.by_chain:
                chains = chain_atoms.unique_residues.unique_chains
                if chains:
                    by_chain.setdefault(struct, {})[chains[0]] = chain_atoms
            for struct, struct_atoms in atoms.by_structure:
                try:
                    for chain, shown_atoms in by_chain[struct].items():
                        chain_atoms = chain.existing_residues.atoms
                        atoms_per_surf.append(
                            (chain_atoms, chain_atoms & shown_atoms, None))
                except KeyError:
                    atoms_per_surf.append((struct_atoms, None, None))
        else:
            for srf in surfaces:
                atoms_per_surf.append((atoms, None, srf))

    # check whether the atoms have charges, and if not, that we know how to assign charges
    # to the requested atoms
    problem_residues = set()
    needs_assignment = set()
    for surf_atoms, shown_atoms, srf in atoms_per_surf:
        for r in surf_atoms.unique_residues:
            if getattr(r, '_coulombic_his_scheme', his_scheme) != his_scheme:
                # should only be set on HIS residues
                needs_assignment.add(r)
            else:
                for a in r.atoms:
                    try:
                        a.charge + 1.0
                    except (AttributeError, TypeError):
                        if r.name in chargeable_residues:
                            needs_assignment.add(r)
                        else:
                            problem_residues.add(r.name)
                        break
    if problem_residues:
        session.logger.status("")
        from chimerax.core.commands import commas
        raise UserError(
            "Don't know how to assign charges to the following residue types: %s"
            % commas(problem_residues, conjunction='and'))

    if needs_assignment:
        session.logger.status("Assigning charges", secondary=True)
        from .coulombic import assign_charges, ChargeError
        try:
            assign_charges(session, needs_assignment, his_scheme)
        except ChargeError as e:
            session.logger.status("")
            raise UserError(str(e))

    # Since electrostatics are long range, unlike mlp, don't compute a map (with a distance cutoff)
    # by default.  Instead, compute the values at the surface vertices directly.  Only compute a
    # map afterward if requested.
    from chimerax.core.undo import UndoState
    undo_state = UndoState('coulombic')
    undo_owners = []
    undo_old_vals = []
    undo_new_vals = []
    for surf_atoms, shown_atoms, srf in atoms_per_surf:
        if srf is None:
            session.logger.status("Creating surface", secondary=True)
            from chimerax.surface import surface
            data = [(surf.atoms, surf) for surf in surface(
                session, surf_atoms if shown_atoms is None else shown_atoms)]
        else:
            data = [(surf_atoms, srf)]
        session.logger.status("Computing electrostatics", secondary=True)
        for charged_atoms, target_surface in data:
            undo_owners.append(target_surface)
            undo_old_vals.append(target_surface.vertex_colors)
            if target_surface.normals is None:
                session.logger.warning(
                    "Surface %s has no vertex normals set, using distance from surface"
                    " of 0 instead of %g" % (target_surface, offset))
                target_points = target_surface.vertices
            else:
                target_points = target_surface.vertices + offset * target_surface.normals
            import numpy, os
            from ._esp import potential_at_points
            cpu_count = os.cpu_count()
            vertex_values = potential_at_points(
                target_surface.scene_position.transform_points(target_points),
                charged_atoms.scene_coords,
                numpy.array([a.charge for a in charged_atoms],
                            dtype=numpy.double), dist_dep, dielectric,
                1 if cpu_count is None else cpu_count)
            rgba = cmap.interpolated_rgba(vertex_values)
            from numpy import uint8
            rgba8 = (255 * rgba).astype(uint8)
            target_surface.vertex_colors = rgba8
            undo_new_vals.append(rgba8)
    undo_state.add(undo_owners,
                   "vertex_colors",
                   undo_old_vals,
                   undo_new_vals,
                   option="S")
    session.undo.register(undo_state)

    session.logger.status("", secondary=True)
    session.logger.status("Finished computing Coulombic charge surface%s" %
                          ("/volume" if map else ""))
Beispiel #7
0
def mlp(session,
        atoms=None,
        method="fauchere",
        spacing=1.0,
        max_distance=5.0,
        nexp=3.0,
        color=True,
        palette=None,
        range=None,
        surfaces=[],
        map=False):
    '''Display Molecular Lipophilic Potential for a single model.

    Parameters
    ----------
    atoms : Atoms
        Color surfaces for these atoms using MLP map.  Only amino acid residues are used.
    method : 'dubost','fauchere','brasseur','buckingham','type5'
        Distance dependent function to use for calculation
    spacing : float
    	Grid spacing, default 1 Angstrom.
    max_distance : float
        Maximum distance from atom to sum lipophilicity.  Default 5 Angstroms.
    nexp : float
        The buckingham method uses this numerical exponent.
    color : bool
        Whether to color molecular surfaces. They are created if they don't yet exist.
    palette : Colormap
        Color palette for coloring surfaces.
        Default is lipophilicity colormap (orange lipophilic, blue lipophobic).
    range : 2-tuple of float
        Range of lipophilicity values defining ends of color map.  Default is -20,20
    surfaces : list of Surface models
        If the color options is true then these surfaces are colored instead of computing surfaces.
    map : bool
        Whether to open a volume model of lipophilicity values
    '''
    if atoms is None:
        from chimerax.atomic import all_atoms
        atoms = all_atoms(session)

    from chimerax.atomic import Residue
    patoms = atoms[atoms.residues.polymer_types == Residue.PT_AMINO]
    if len(patoms) == 0:
        from chimerax.core.errors import UserError
        raise UserError('mlp: no amino acids specified')

    if palette is None:
        from chimerax.core.colors import BuiltinColormaps
        cmap = BuiltinColormaps['lipophilicity']
    else:
        cmap = palette
    if range is None and not cmap.values_specified:
        range = (-20, 20)

    # Color surfaces by lipophilicity
    if color:
        # Compute surfaces if not already created
        from chimerax.surface import surface
        surfs = surface(session, patoms) if len(surfaces) == 0 else surfaces
        from chimerax.core.undo import UndoState
        undo_state = UndoState('mlp')
        for s in surfs:
            satoms = s.atoms
            name = 'mlp ' + s.name.split(maxsplit=1)[0]
            v = mlp_map(session,
                        satoms,
                        method,
                        spacing,
                        max_distance,
                        nexp,
                        name,
                        open_map=map)
            from chimerax.surface import color_surfaces_by_map_value
            color_surfaces_by_map_value(satoms,
                                        map=v,
                                        palette=cmap,
                                        range=range,
                                        undo_state=undo_state)
        session.undo.register(undo_state)
    else:
        name = 'mlp map'
        v = mlp_map(session,
                    patoms,
                    method,
                    spacing,
                    max_distance,
                    nexp,
                    name,
                    open_map=map)
Beispiel #8
0
def find_clashes(session,
                 test_atoms,
                 assumed_max_vdw=2.1,
                 attr_name=defaults["attr_name"],
                 bond_separation=defaults["bond_separation"],
                 clash_threshold=defaults["clash_threshold"],
                 distance_only=None,
                 hbond_allowance=defaults["clash_hbond_allowance"],
                 inter_model=True,
                 inter_submodel=False,
                 intra_model=True,
                 intra_res=False,
                 intra_mol=True,
                 res_separation=None,
                 restrict="any"):
    """Detect steric clashes/contacts

       'test_atoms' should be an Atoms collection.

       'restrict' can be one of:
         - 'any':  interactions involving at least one atom from 'test_atoms' will be found
         - 'both':  interactions involving only atoms from 'test_atoms' will be found
         - 'cross':  interactions involving exactly one atom from 'test_atoms' will be found
         - an Atoms collection :  interactions between 'test_atoms' and the 'restrict' atoms will be found
       The "clash value" is the sum of the VDW radii minus the distance, which must exceed 'clash_threshold'.

       'hbond_allowance' is how much the clash value is reduced if one
       atom is a donor and the other an acceptor.

       If 'distance_only' is set (in which case it must be a positive numeric
       value), then both VDW radii, clash_threshold, and hbond_allowance are
       ignored and the center-center distance between the atoms must be <= the given value.

       Atom pairs are eliminated from consideration if they are less than
       or equal to 'bond_separation' bonds apart.

       Intra-residue clashes are ignored unless intra_res is True.
       Intra-model clashes are ignored unless intra_model is True.
       Intra-molecule (covalently connected fragment) clashes are ignored
       unless intra_mol is True.
       Inter-(sibling)submodel clashes are ignored unless inter_submodel is True.
       Inter-model clashes are ignored unless inter_model is True.

       If res_separation is not None, it should be a positive integer -- in which
       case for residues in the same chain, clashes/contacts are ignored unless
       the residues are at least that far apart in the sequence.

       Returns a dictionary keyed on atoms, with values that are
       dictionaries keyed on clashing atom with value being the clash value.
    """

    from chimerax.atomic import Structure
    use_scene_coords = inter_model and len(
        [m for m in session.models if isinstance(m, Structure)]) > 1
    # use the fast _closepoints module to cut down candidate atoms if we
    # can (since _closepoints doesn't know about "non-bonded" it isn't as
    # useful as it might otherwise be)
    if restrict == "any":
        if inter_model:
            from chimerax.atomic import all_atoms
            search_atoms = all_atoms(session)
        else:
            from chimerax.atomic import structure_atoms
            search_atoms = structure_atoms(test_atoms.unique_structures)
    elif restrict == "cross":
        if inter_model:
            from chimerax.atomic import all_atoms
            universe_atoms = all_atoms(session)
        else:
            from chimerax.atomic import structure_atoms
            universe_atoms = structure_atoms(test_atoms.unique_structures)
        other_atoms = universe_atoms.subtract(test_atoms)
        if distance_only:
            cutoff = distance_only
        else:
            cutoff = 2.0 * assumed_max_vdw - clash_threshold
        if use_scene_coords:
            test_coords = test_atoms.scene_coords
            other_coords = other_atoms.scene_coords
        else:
            test_coords = test_atoms.coords
            other_coords = other_atoms.coords
        from chimerax.geometry import find_close_points
        t_close, o_close = find_close_points(test_coords, other_coords, cutoff)
        test_atoms = test_atoms[t_close]
        search_atoms = other_atoms[o_close]
    elif not isinstance(restrict, str):
        search_atoms = restrict
    else:
        search_atoms = test_atoms

    if res_separation is not None:
        chain_pos = {}
        for s in test_atoms.unique_structures:
            for c in s.chains:
                for i, r in enumerate(c.residues):
                    if r:
                        chain_pos[r] = i
    from chimerax.atom_search import AtomSearchTree
    tree = AtomSearchTree(search_atoms, scene_coords=inter_model)
    clashes = {}
    from chimerax.geometry import distance
    intra_mol_map = {}
    for a in test_atoms:
        if distance_only:
            cutoff = distance_only
        else:
            cutoff = a.radius + assumed_max_vdw - clash_threshold
        crd = a.scene_coord if use_scene_coords else a.coord
        nearby = tree.search(crd, cutoff)
        if not nearby:
            continue
        need_expansion = [a]
        exclusions = set(need_expansion)
        for i in range(bond_separation):
            next_need = []
            for expand in need_expansion:
                for n in expand.neighbors:
                    if n in exclusions:
                        continue
                    exclusions.add(n)
                    next_need.append(n)
            need_expansion = next_need
        if not intra_mol and a not in intra_mol_map:
            connected = set([a])
            to_do = list(a.neighbors)
            while to_do:
                conn = to_do.pop()
                connected.add(conn)
                for nb in conn.neighbors:
                    if nb not in connected:
                        to_do.append(nb)
            for ca in connected:
                intra_mol_map[ca] = connected
        for nb in nearby:
            if nb in exclusions:
                continue
            if not intra_res and a.residue == nb.residue:
                continue
            if not intra_mol and nb in intra_mol_map[a]:
                continue
            if not inter_model and a.structure != nb.structure:
                continue
            if not intra_model and a.structure == nb.structure:
                continue
            if a in clashes and nb in clashes[a]:
                continue
            if res_separation is not None:
                if a.residue.chain is not None and a.residue.chain == nb.residue.chain:
                    if abs(chain_pos[a.residue] -
                           chain_pos[nb.residue]) < res_separation:
                        continue
            if not inter_submodel \
            and a.structure.id and nb.structure.id \
            and a.structure.id[0] == nb.structure.id[0] \
            and a.structure.id[:-1] == nb.structure.id[:-1] \
            and a.structure.id[1:] != nb.structure.id[1:]:
                continue
            if use_scene_coords:
                a_crd, nb_crd = a.scene_coord, nb.scene_coord
            else:
                a_crd, nb_crd = a.coord, nb.coord
            if distance_only:
                clash = distance_only - distance(a_crd, nb_crd)
            else:
                clash = a.radius + nb.radius - distance(a_crd, nb_crd)
            if hbond_allowance and not distance_only:
                if (_donor(a) and _acceptor(nb)) or (_donor(nb)
                                                     and _acceptor(a)):
                    clash -= hbond_allowance
            if distance_only:
                if clash < 0.0:
                    continue
            elif clash < clash_threshold:
                continue
            clashes.setdefault(a, {})[nb] = clash
            clashes.setdefault(nb, {})[a] = clash
    return clashes
Beispiel #9
0
def _cmd(session,
         test_atoms,
         name,
         hbond_allowance,
         overlap_cutoff,
         test_type,
         color,
         radius,
         *,
         attr_name=defaults["attr_name"],
         bond_separation=defaults["bond_separation"],
         continuous=False,
         dashes=None,
         distance_only=None,
         inter_model=True,
         inter_submodel=False,
         intra_model=True,
         intra_mol=defaults["intra_mol"],
         intra_res=defaults["intra_res"],
         log=defaults["action_log"],
         make_pseudobonds=defaults["action_pseudobonds"],
         naming_style=None,
         res_separation=None,
         restrict="any",
         reveal=False,
         save_file=None,
         set_attrs=defaults["action_attr"],
         select=defaults["action_select"],
         show_dist=False,
         summary=True):
    from chimerax.core.errors import UserError
    if test_atoms is None:
        from chimerax.atomic import AtomicStructure, AtomicStructures
        test_atoms = AtomicStructures([
            s for s in session.models if isinstance(s, AtomicStructure)
        ]).atoms
    if not test_atoms:
        raise UserError("No atoms match given atom specifier")
    from chimerax.core.colors import Color
    if color is not None and not isinstance(color, Color):
        color = Color(rgba=color)
    from chimerax.atomic import get_triggers
    ongoing = False
    if continuous:
        if set_attrs or save_file != None or log:
            raise UserError(
                "log/setAttrs/saveFile not allowed with continuous detection")
        if getattr(session, _continuous_attr, None) == None:
            from inspect import getargvalues, currentframe, getfullargspec
            arg_names, fArgs, fKw, frame_dict = getargvalues(currentframe())
            arg_spec = getfullargspec(_cmd)
            args = [frame_dict[an] for an in arg_names[:len(arg_spec.args)]]
            kw = {k: frame_dict[k] for k in arg_names[len(arg_spec.args):]}
            call_data = (args, kw)

            def changes_cb(trig_name,
                           changes,
                           session=session,
                           call_data=call_data):
                s_reasons = changes.atomic_structure_reasons()
                a_reasons = changes.atom_reasons()
                if 'position changed' in s_reasons \
                or 'active_coordset changed' in s_reasons \
                or 'coord changed' in a_reasons \
                or 'alt_loc changed' in a_reasons:
                    args, kw = call_data
                    if not args[1]:
                        # all atoms gone
                        delattr(session, _continuous_attr)
                        from chimerax.core.triggerset import DEREGISTER
                        return DEREGISTER
                    _cmd(*tuple(args), **kw)

            setattr(session, _continuous_attr,
                    get_triggers().add_handler('changes', changes_cb))
        else:
            ongoing = True
    elif getattr(session, _continuous_attr, None) != None:
        get_triggers().remove_handler(getattr(session, _continuous_attr))
        delattr(session, _continuous_attr)
    from .clashes import find_clashes
    clashes = find_clashes(session,
                           test_atoms,
                           attr_name=attr_name,
                           bond_separation=bond_separation,
                           clash_threshold=overlap_cutoff,
                           distance_only=distance_only,
                           hbond_allowance=hbond_allowance,
                           inter_model=inter_model,
                           inter_submodel=inter_submodel,
                           intra_model=intra_model,
                           intra_res=intra_res,
                           intra_mol=intra_mol,
                           res_separation=res_separation,
                           restrict=restrict)
    if select:
        session.selection.clear()
        for a in clashes.keys():
            a.selected = True
    # if relevant, put the test_atoms in the first column
    if restrict == "both":
        output_grouping = set()
    else:
        output_grouping = test_atoms
    test_type = "distances" if distance_only else test_type
    info = (overlap_cutoff, hbond_allowance, bond_separation, intra_res,
            intra_mol, clashes, output_grouping, test_type, res_separation)
    if log:
        import io
        buffer = io.StringIO()
        buffer.write("<pre>")
        _file_output(buffer, info, naming_style)
        buffer.write("</pre>")
        session.logger.info(buffer.getvalue(), is_html=True)
    if save_file is not None:
        _file_output(save_file, info, naming_style)
    if summary:
        if clashes:
            total = 0
            for clash_list in clashes.values():
                total += len(clash_list)
            session.logger.status("%d %s" % (total / 2, test_type),
                                  log=not ongoing)
        else:
            session.logger.status("No %s" % test_type, log=not ongoing)
    if not (set_attrs or make_pseudobonds or reveal):
        _xcmd(session, name)
        return clashes
    from chimerax.atomic import all_atoms
    if restrict == "both":
        attr_atoms = test_atoms
    elif restrict in ("any", "cross"):
        if inter_model:
            attr_atoms = all_atoms(session)
        else:
            attr_atoms = test_atoms.unique_structures.atoms
    else:
        from chimerax.atomic import concatenate
        attr_atoms = concatenate([test_atoms, restrict],
                                 remove_duplicates=True)
    from chimerax.atomic import Atoms
    clash_atoms = Atoms([a for a in attr_atoms if a in clashes])
    if set_attrs:
        # delete the attribute in _all_ atoms...
        for a in all_atoms(session):
            if hasattr(a, attr_name):
                delattr(a, attr_name)
        for a in clash_atoms:
            clash_vals = list(clashes[a].values())
            clash_vals.sort()
            setattr(a, attr_name, clash_vals[-1])
    if reveal:
        # display sidechain or backbone as appropriate for undisplayed atoms
        reveal_atoms = clash_atoms.filter(clash_atoms.displays == False)
        reveal_residues = reveal_atoms.unique_residues
        sc_rv_atoms = reveal_atoms.filter(reveal_atoms.is_side_chains == True)
        if sc_rv_atoms:
            sc_residues = sc_rv_atoms.unique_residues
            sc_res_atoms = sc_residues.atoms
            sc_res_atoms.filter(
                sc_res_atoms.is_side_chains == True).displays = True
            reveal_residues = reveal_residues - sc_residues
        bb_rv_atoms = reveal_atoms.filter(reveal_atoms.is_backbones() == True)
        if bb_rv_atoms:
            bb_residues = bb_rv_atoms.unique_residues
            bb_res_atoms = bb_residues.atoms
            bb_res_atoms.filter(
                bb_res_atoms.is_backbones() == True).displays = True
            reveal_residues = reveal_residues - bb_residues
        # also reveal non-polymeric atoms
        reveal_residues.atoms.displays = True
    if make_pseudobonds:
        if len(attr_atoms.unique_structures) > 1:
            pbg = session.pb_manager.get_group(name)
        else:
            pbg = attr_atoms[0].structure.pseudobond_group(name)
        pbg.clear()
        pbg.radius = radius
        if color is not None:
            pbg.color = color.uint8x4()
        if dashes is not None:
            pbg.dashes = dashes
        seen = set()
        for a in clash_atoms:
            seen.add(a)
            for clasher in clashes[a].keys():
                if clasher in seen:
                    continue
                pbg.new_pseudobond(a, clasher)
        if show_dist:
            session.pb_dist_monitor.add_group(pbg)
        else:
            session.pb_dist_monitor.remove_group(pbg)
        if pbg.id is None:
            session.models.add([pbg])
    else:
        _xcmd(session, name)
    return clashes