def _evaluate(self, postfix, prnlev): """ Evaluates a postfix in RPN format and returns a selection array """ from sys import stderr buffer = '' stack = [] pos = 0 # position in postfix while pos < len(postfix): p = postfix[pos] if p == '[': buffer = '' elif p == ']': # end of the token ptoken = buffer pmask = self._selectElemMask(ptoken) stack.append(pmask) elif self._isOperand(p) or p in [':', '@']: buffer += p elif p in ['&', '|']: pmask1 = None pmask2 = None try: pmask1 = stack.pop() pmask2 = stack.pop() pmask = self._binop(p, pmask1, pmask2) except IndexError: raise MaskError('Illegal binary operation') stack.append(pmask) elif p in ['<', '>']: if pos < len(postfix) - 1 and postfix[pos + 1] in [':', '@']: buffer += p else: try: pmask1 = stack.pop() # distance criteria pmask2 = stack.pop() pmask = self._selectDistd(pmask1, pmask2) except IndexError: return [0 for a in self.parm.atoms] stack.append(pmask) elif p == '!': try: pmask1 = stack.pop() except IndexError: raise MaskError('Illegal ! operation') pmask = self._neg(pmask1) stack.append(pmask) else: raise MaskError('Unknown symbol evaluating RPN: %s' % p) pos += 1 # end while i < len(postfix) pmask = stack.pop() if stack: raise MaskError('There may be missing operands in the mask!') if prnlev > 7: stderr.write('%d atoms selected by %s' % (sum(pmask), self.mask)) return pmask
def _torpn(self, infix, prnlev): """ Converts the infix to an RPN array """ postfix = '' stack = ['_'] # use a list as a stack. Then pop() works as expected flag = 0 i = 0 while i < len(infix): p = infix[i] if p == '[': postfix += p flag = 1 elif p == ']': postfix += p flag = 0 elif flag: postfix += p elif p == '(': stack.append(p) elif p == ')': pp = stack.pop() while pp != '(': if pp == '_': raise MaskError('Unbalanced parentheses in Mask.') postfix += pp pp = stack.pop() # At this point both ()s are discarded elif p == '_': pp = stack.pop() while pp != '_': if pp == '(': raise MaskError('Unbalanced parentheses in Mask.') postfix += pp pp = stack.pop() elif self._isOperator(p): P1 = self._priority(p) P2 = self._priority(stack[len(stack) - 1]) if P1 > P2: stack.append(p) else: while P1 <= P2: pp = stack.pop() postfix += pp P1 = self._priority(p) P2 = self._priority(stack[len(stack) - 1]) stack.append(p) else: raise MaskError('Unknown symbol %s' % p) i += 1 # end while i < len(infix): return postfix
def _selectDistd(self, pmask1, pmask2): """ Selects atoms based on a distance criteria """ # pmask1 is either @<number> or :<number>, and represents the distance # criteria. pmask2 is the selection of atoms from which the distance is # evaluated. pmask = _mask(len(self.parm.atoms)) # Determine if we want > or < if pmask1[0] == '<': cmp = float.__lt__ elif pmask1[0] == '>': cmp = float.__gt__ else: raise MaskError( 'Unknown comparison criteria for distance mask: %s' % pmask1[0]) pmask1 = pmask1[1:] if pmask1[0] not in ':@': raise MaskError('Bad distance criteria for mask: %s' % pmask1) try: distance = float(pmask1[1:]) except TypeError: raise MaskError('Distance must be a number: %s' % pmask1[1:]) if not hasattr(self.parm.atoms[0], 'xx'): raise MaskError('Distance-based masks require loaded coordinates.') distance *= distance # Faster to compare square of distance # First select all atoms that satisfy the distance. If we ended up # choosing residues, then we will go back through afterwards and select # entire residues when one of the atoms in that residue is selected. idxlist = [i for i, val in enumerate(pmask2) if val == 1] for i, atomi in enumerate(self.parm.atoms): for j in idxlist: atomj = self.parm.atoms[j] dx = atomi.xx - atomj.xx dy = atomi.xy - atomj.xy dz = atomi.xz - atomj.xz d2 = dx * dx + dy * dy + dz * dz if cmp(d2, distance): pmask[i] = 1 break # Now see if we have to select all atoms in residues with any selected # atoms if pmask1[0] == ':': for res in self.parm.residues: for atom in res.atoms: if pmask[atom.idx] == 1: for atom in res.atoms: pmask[atom.idx] = 1 break return pmask
def Or(self, other): if self.natom != other.natom: raise MaskError('_mask: or() requires another mask of equal size!') new_mask = _mask(self.natom) for i in xrange(len(self)): new_mask[i] = int(self[i] or other[i]) return new_mask
def _binop(self, op, pmask1, pmask2): """ Does a binary operation on a pair of masks """ if op == '&': return pmask1.And(pmask2) if op == '|': return pmask1.Or(pmask2) raise MaskError('Unknown operator [%s]' % op)
def _residue_numlist(self, instring, mask): """ Fills a _mask based on residue numbers """ buffer = '' pos = 0 at1 = at2 = dash = 0 while pos < len(instring): p = instring[pos] if p.isdigit(): buffer += p if p == ',' or pos == len(instring) - 1: if dash == 0: at1 = int(buffer) self._resnum_select(at1, at1, mask) else: at2 = int(buffer) self._resnum_select(at1, at2, mask) dash = 0 buffer = '' elif p == '-': at1 = int(buffer) dash = 1 buffer = '' if not (p.isdigit() or p in [',', '-']): raise MaskError('Unknown symbol in residue number ' 'parsing [%s]' % p) pos += 1
def And(self, other): if self.natom != other.natom: raise MaskError( "_mask: and() requires another mask of equal size!") new_mask = _mask(self.natom) for i in xrange(len(self)): new_mask[i] = int(self[i] and other[i]) return new_mask
def _priority(self, op): if op in ['>', '<']: return 6 if op in ['!']: return 5 if op in ['&']: return 4 if op in ['|']: return 3 if op in ['(']: return 2 if op in ['_']: return 1 raise MaskError('Unknown operator [%s] in Mask ==%s==' % (op, self.mask))
def _residue_namelist(self, instring, mask): """ Fills a _mask based on residue names """ buffer = '' pos = 0 while pos < len(instring): p = instring[pos] if p.isalnum() or p in ['*', '?', '+', "'", '-']: buffer += p if p == ',' or pos == len(instring) - 1: if '-' in buffer and buffer[0].isdigit(): self._residue_numlist(buffer, mask) else: self._resname_select(buffer, mask) buffer = '' if not (p.isalnum() or p in ",?*'+-"): raise MaskError('Unknown symbol in residue name ' 'parsing [%s]' % p) pos += 1
def _atom_namelist(self, instring, mask, key='name'): """ Fills a _mask based on atom names/types """ buffer = '' pos = 0 while pos < len(instring): p = instring[pos] if p.isalnum() or p in "\\*?+'-": buffer += p if p == ',' or pos == len(instring) - 1: if '-' in buffer and buffer[0].isdigit(): self._atom_numlist(buffer, mask) else: self._atname_select(buffer, mask, key) buffer = '' if not (p.isalnum() or p in "\\,?*'+-"): raise MaskError('Unrecognized symbol in atom name ' 'parsing [%s]' % p) pos += 1
def _tokenize(self, prnlev): """ Tokenizes the mask string into individual selections: 1. remove spaces 2. isolate 'operands' into brackets [...] 3. split expressions of the type :1-10@CA,CB into 2 parts; the 2 parts are joned with & operator and (for the sake of preserving precedence of other operators) enclosed by (...); i.e. :1-10@CA,CB is split into (:1-10 & @CA,CB) 4. do basic error checking """ buffer = '' # keeping track of a single operand infix = '' # value that is returned at the end # flag == 0: means new operand or operand was completed & ended with ] # flag == 1: means operand with ":" read # flag == 2: means operand with "@" read # flag == 3: means '<' or '>' read, waiting for numbers flag = 0 i = 0 while i < len(self.mask): p = self.mask[i] # skip whitespace if p.isspace(): i += 1 continue # If p is an operator, is the last character, or is a ()... elif (self._isOperator(p) or i == len(self.mask) - 1 or p in ['(', ')']): # Deal with the last character being a wildcard that we have to # convert if p == '=' and i == len(self.mask) - 1: # wildcard if flag > 0: p = '*' else: raise MaskError("AmberMask: '=' not in name " "list syntax") # If this is the end of an operand, terminate the buffer, flush # it to infix, and reset flag to 0 and empty the buffer if flag > 0: if i == len(self.mask) - 1 and p != ')': buffer += p buffer += '])' flag = 0 infix += buffer buffer = '' if i != len(self.mask) - 1 or p == ')': infix += p # else if p is >,< if p in ['<', '>']: buffer = '([%s' % p i += 1 p = self.mask[i] buffer += p flag = 3 try: self.parm.coords[0] except AttributeError: raise MaskError('<,> operators require coordinates') if not p in [':', '@']: raise MaskError('Bad syntax [%s]' % self.mask) elif self._isOperand(p): if flag == 0: buffer = '([' flag = 1 if p != '*': raise MaskError('Bad syntax [%s]' % self.mask) if p == '=': # wildcard if flag > 0: p = '*' else: raise MaskError("'=' not in name list syntax") buffer += p elif p == ':': if flag == 0: buffer = '([:' flag = 1 else: buffer += '])|([:' flag = 1 elif p == '@': if flag == 0: buffer = '([@' flag = 2 elif flag == 1: buffer += ']&[@' flag = 2 elif flag == 2: buffer += '])|([@' flag = 2 else: raise MaskError('Unknown symbol (%s) expression' % p) i += 1 # end while i < len(self.mask): # Check that each operand has at least 4 characters: [:1] and [@C], etc. i = 0 n = 1 # number of characters in current operand flag = 0 while i < len(infix): p = infix[i] if p == '[': n += 1 flag = 1 elif p == ']': if n < 4 and infix[i - 1] != '*': raise MaskError('empty token in infix') n = 1 else: if flag == 1: n += 1 i += 1 return infix + '_' # terminating _ for next step
def remove(self, *args, **kwargs): raise MaskError('_mask is a fixed-length array!')
def extend(self, *args, **kwargs): raise MaskError('_mask is a fixed-length array!')
def _selectElemMask(self, ptoken): """ Selects an element mask """ # some constants ALL = 0 NUMLIST = 1 NAMELIST = 2 TYPELIST = 3 ELEMLIST = 4 # define the mask object and empty buffer pmask = _mask(len(self.parm.atoms)) buffer = '' buffer_p = 0 # This is a residue NUMber LIST if ptoken.startswith(':'): reslist = NUMLIST pos = 1 while pos < len(ptoken): p = ptoken[pos] buffer += p buffer_p += 1 if p == '*' and ptoken[pos - 1] != '\\': if buffer_p == 1 and (pos == len(ptoken) - 1 or ptoken[pos + 1] == ','): reslist = ALL elif reslist == NUMLIST: reslist = NAMELIST elif p.isalpha() or p in '?*': reslist = NAMELIST if pos == len(ptoken) - 1: buffer_p = 0 if len(buffer) != 0 and buffer_p == 0: if reslist == ALL: pmask.select_all() elif reslist == NUMLIST: self._residue_numlist(buffer, pmask) elif reslist == NAMELIST: self._residue_namelist(buffer, pmask) reslist = NUMLIST pos += 1 elif ptoken.startswith('@'): atomlist = NUMLIST pos = 1 while pos < len(ptoken): p = ptoken[pos] buffer += p buffer_p += 1 if p == '*' and ptoken[pos - 1] != "\\": if buffer_p == 0 and (pos == len(ptoken) - 1 or ptoken[pos + 1] == ','): atomlist = ALL elif atomlist == NUMLIST: atomlist = NAMELIST elif p.isalpha() or p in '?*': if atomlist == NUMLIST: atomlist = NAMELIST elif p == '%': atomlist = TYPELIST elif p == '/': atomlist = ELEMLIST if pos == len(ptoken) - 1: buffer_p = 0 if len(buffer) != 0 and buffer_p == 0: if atomlist == ALL: pmask.select_all() elif atomlist == NUMLIST: self._atom_numlist(buffer, pmask) elif atomlist == NAMELIST: self._atom_namelist(buffer, pmask) elif atomlist == TYPELIST: self._atom_typelist(buffer[1:], pmask) elif atomlist == ELEMLIST: self._atom_elemlist(buffer[1:], pmask) pos += 1 elif ptoken.strip() == '*': pmask.select_all() elif ptoken[0] in ['<', '>']: return ptoken else: raise MaskError('Mask is missing : and @') # end if ':' in ptoken: return pmask