Source code for sknano.core.atoms.selections

# -*- coding: utf-8 -*-
"""
===============================================================================
Atom selection classes (:mod:`sknano.core.atoms.selections`)
===============================================================================

.. currentmodule:: sknano.core.atoms.selections

"""
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
__docformat__ = 'restructuredtext en'

from functools import reduce
from importlib import import_module
from operator import and_, or_

import numpy as np

# from abc import abstractmethod

from pyparsing import CaselessKeyword, Forward, Keyword, OneOrMore, Optional, \
    ParseException, Suppress, Word, alphas, delimitedList, infixNotation, \
    oneOf, opAssoc

from sknano.core import BaseClass, binary_operator, integer, kwargs_expr, \
    number
# from sknano.core.geometric_regions import Sphere
# from .md_atoms import MDAtom as Atom, MDAtoms as Atoms

__all__ = ['SelectionException', 'SelectionParser',
           'generate_vmd_selection_string']


def make_selection(cls):
    def selection(s, loc, tokens):
        return cls(tokens)
    return selection


def as_region(s, l, t):
    return \
        getattr(import_module('sknano.core.geometric_regions'), t[0])(**t[1])


class Selection(BaseClass):
    """Base selection class."""
    def __init__(self, selection):
        super().__init__()
        self.selection = selection
        self.fmtstr = "{selection!r}"

    def apply(self, atoms, mask):
        if isinstance(mask, np.ndarray) and (mask.dtype in (bool, int)):
            return atoms.filtered(mask)
        else:
            return atoms.__class__(atoms=mask[:], **atoms.kwargs)

    def todict(self):
        return dict(selection=self.selection)


[docs]class SelectionException(ParseException): """Custom :class:`Exception` class for :class:`Selection`\ s.""" pass
class AndSelection(Selection): def apply(self, atoms, as_mask=False): mask = None try: mask = reduce(lambda lsel, rsel: np.bitwise_and(lsel, rsel), [sel.apply(atoms, as_mask=True) for sel in self.selection[0]]) if as_mask: return mask except (TypeError, ValueError): filtered = reduce(lambda lsel, rsel: and_(lsel, rsel), [sel.apply(atoms) for sel in self.selection[0]]) if mask is not None: return super().apply(atoms, mask) else: return super().apply(atoms, filtered) class OrSelection(Selection): def apply(self, atoms, as_mask=False): mask = None try: mask = reduce(lambda lsel, rsel: np.bitwise_or(lsel, rsel), [sel.apply(atoms, as_mask=True) for sel in self.selection[0]]) if as_mask: return mask except (TypeError, ValueError): filtered = reduce(lambda lsel, rsel: or_(lsel, rsel), [sel.apply(atoms) for sel in self.selection[0]]) if mask is not None: return super().apply(atoms, mask) else: return super().apply(atoms, filtered) class NotSelection(Selection): def apply(self, atoms): return atoms[:] - self.selection[0][0].apply(atoms[:]) class AllSelection(Selection): def apply(self, atoms): return atoms[:] class NoneSelection(Selection): def apply(self, atoms): # return super().apply(atoms, []) return None class IDSelection(Selection): def apply(self, atoms, as_mask=False): mask = np.in1d(atoms.ids, self.selection.asList()).nonzero()[0] if as_mask: return mask return super().apply(atoms, mask) class MolIDSelection(Selection): def apply(self, atoms, as_mask=False): mask = np.in1d(atoms.mol_ids, self.selection.asList()).nonzero()[0] if as_mask: return mask return super().apply(atoms, mask) class TypeSelection(Selection): def apply(self, atoms, as_mask=False): mask = np.in1d(atoms.types, self.selection.asList()).nonzero()[0] if as_mask: return mask return super().apply(atoms, mask) class AttributeSelection(Selection): def apply(self, atoms, as_mask=False): try: attr, op, val = self.selection if hasattr(atoms, attr): mask = op(getattr(atoms, attr), val) else: mask = \ np.asarray([op(getattr(atom, attr), val) for atom in atoms]) except ValueError: attr, val = self.selection if hasattr(atoms, attr + 's'): mask = getattr(atoms, attr + 's') == val elif hasattr(atoms, attr): mask = getattr(atoms, attr) == val else: mask = \ np.asarray([True if getattr(atom, attr) == val else False for atom in atoms]) if as_mask: return mask else: return super().apply(atoms, mask) class WithinSelection(Selection): def apply(self, atoms): try: other = self.selection[-1].apply(atoms) filtered = atoms.query_ball_tree(other, self.selection[0]) return super().apply(atoms, filtered) except AttributeError: region = self.selection[-1] mask = \ np.asarray([region.contains(atom.r) for atom in atoms]) return super().apply(atoms, mask) class ExWithinSelection(Selection): def apply(self, atoms): other = self.selection[-1].apply(atoms) filtered = atoms.query_ball_tree(other, self.selection[0]) filtered = super().apply(atoms, filtered) - other return super().apply(atoms, filtered)
[docs]class SelectionParser(BaseClass): """Selection parser class.""" ALL = CaselessKeyword('all') NONE = CaselessKeyword('none') NAME = CaselessKeyword('name') TYPE = CaselessKeyword('type') INDEX = CaselessKeyword('index') ID = CaselessKeyword('id') MOLID = CaselessKeyword('molid') | CaselessKeyword('mol') WITHIN = CaselessKeyword('within') EXWITHIN = CaselessKeyword('exwithin') SERIAL = CaselessKeyword('serial') ATOMICNUMBER = CaselessKeyword('atomicnumber') ELEMENT = CaselessKeyword('element') RESIDUE = CaselessKeyword('residue') NUMBONDS = CaselessKeyword('numbonds') OF = CaselessKeyword('of') NOT = CaselessKeyword('not') AND = CaselessKeyword('and') OR = CaselessKeyword('or') LPAR, RPAR = map(Suppress, '()') ATOM_ATTRIBUTE = \ oneOf(' '.join(('x', 'y', 'z', 'r', 'vx', 'vy', 'vz', 'v', 'fx', 'fy', 'fz', 'f', 'Z', 'element', 'symbol', 'mass', 'CN'))) PARALLELEPIPED, CUBOID, CUBE, ELLIPSOID, SPHERE, CYLINDER, CONE = \ map(Keyword, ['Parallelepiped', 'Cuboid', 'Cube', 'Ellipsoid', 'Sphere', 'Cylinder', 'Cone']) REGIONS = (PARALLELEPIPED | CUBOID | CUBE | ELLIPSOID | SPHERE | CYLINDER | CONE) region_expression = \ (REGIONS + LPAR + kwargs_expr + RPAR).setParseAction(as_region) # selection_expression = Forward() expr_term = Forward() expr = \ infixNotation(expr_term, [(Suppress(NOT), 1, opAssoc.RIGHT, NotSelection), (Suppress(AND), 2, opAssoc.LEFT, AndSelection), (Suppress(OR), 2, opAssoc.LEFT, OrSelection)]) grouped_expr = LPAR + expr_term + RPAR all_selection_expression = (Suppress(ALL)).setParseAction(AllSelection) none_selection_expression = (Suppress(NONE)).setParseAction(NoneSelection) attr_selection_expression = \ (ATOM_ATTRIBUTE + Optional(binary_operator) + (number | Word(alphas)) ).setParseAction(AttributeSelection) id_selection_expression = \ (Suppress(ID) + delimitedList(OneOrMore(integer), delim=' ') ).setParseAction(IDSelection) molid_selection_expression = \ (Suppress(MOLID) + delimitedList(OneOrMore(integer), delim=' ') ).setParseAction(MolIDSelection) type_selection_expression = \ (Suppress(TYPE) + delimitedList(OneOrMore(integer), delim=' ') ).setParseAction(TypeSelection) within_selection_expression = \ (Suppress(WITHIN) + ((number + Suppress(OF) + expr) | region_expression) ).setParseAction(WithinSelection) exwithin_selection_expression = \ (Suppress(EXWITHIN) + number + Suppress(OF) + expr ).setParseAction(ExWithinSelection) expr_term << \ (all_selection_expression | none_selection_expression | attr_selection_expression | id_selection_expression | molid_selection_expression | type_selection_expression | within_selection_expression | exwithin_selection_expression | grouped_expr) selection_expression = expr.copy() def __init__(self, atoms=None, selstr=None, **kwargs): super().__init__(**kwargs) self.atoms = atoms self.selstr = selstr self.fmtstr = "atoms={atoms!r}, selstr={selstr!r}" # if selstr is not None: # self.parse(selstr)
[docs] def parse(self, selstr=None, **kwargs): """Parse `selstr`.""" if selstr is None and self.selstr is not None: selstr = self.selstr try: selection = \ self.selection_expression.parseString(selstr, parseAll=True)[0] if self.verbose: print('selstr: {}'.format(selstr)) print('selection: {}'.format(selection)) except ParseException as e: raise SelectionException(e.pstr, e.loc, e.msg, e.parseElement) else: return selection.apply(self.atoms)
[docs] def todict(self): return dict(atoms=self.atoms, selstr=self.selstr)
[docs]def generate_vmd_selection_string(keyword, iterable): """Generate a VMD `keyword` selection string from iterable.""" return ' '.join((keyword, ' '.join(map(str, iterable))))