B
    bk                 @   sh   d Z ddlmZmZmZ ddlmZ ddlmZ ddl	m
Z
 G dd deZd	d
 ZG dd deZdS )z
Module for evaluating Amber Mask strings and translating them into lists in
which a selected atom is 1 and one that's not is 0.
    )divisionprint_functionabsolute_import   )	MaskError)	AtomicNum)rangec               @   s   e Zd ZdZdd Zdd Zd6ddZd7d
dZdd Zdd Z	dd Z
dd Zdd Zdd Zdd Zdd Zdd Zd8dd Zd!d" Zd#d$ Zd%d& Zd'd( Zd)d* Zd+d, Zd9d-d.Zd/d0 Zd1d2 Zd3d4 Zd5S ):	AmberMaska  
    What is hopefully a fully-fledged Amber mask parser implemented in Python.

    Parameters
    ----------
    parm : Structure
        The topology structure for which to select atoms
    mask : str
        The mask string that selects a subset of atoms
    c             C   s   || _ | | _d S )N)parmstripmask)selfr
   r    r   0lib/python3.7/site-packages/parmed/amber/mask.py__init__   s    zAmberMask.__init__c             C   s   | j S )N)r   )r   r   r   r   __str__!   s    zAmberMask.__str__Fc             c   s,   x&t | j|dD ]\}}|r|V  qW dS )a   Generator that returns the indexes of selected atoms

        Parameters
        ----------
        invert : bool, optional
            If True, all atoms *not* selected by the mask will be returned

        Returns
        -------
        generator of int
            Each iteration will yield the index of the next atom that has been
            selected by the mask. Atom indices are 0-based
        )invertN)	enumerate	Selection)r   r   ivr   r   r   Selected&   s    zAmberMask.Selectedr   c             C   s   ddl m}m} |dkr"|d |dkr:|d| j  | j dkrZdd	 | jjD S | |}|dkrz|d
|  | 	||}|dkr|d|  |rdd	 | 
||D S | 
||S )a  
        Parses the mask and analyzes the result to return an atom
        selection array

        Parameters
        ----------
        prnlev : int, optional
            Print debug information on the processing of the Amber mask string.
            This is mainly useful if you are modifying the mask parser. Default
            value is 0 (no printout), values between 1 and 8 control the level
            of output (larger values produce more output). Default 0
        invert : bool, optional
            If True, the returned array will invert the selection of the mask
            (i.e., selected atoms will not be selected and vice-versa)

        Returns
        -------
        mask : list of int
            A list with length equal to the number of atoms in the assigned
            :class:`Structure <parmed.structure.Structure>` instance. Selected
            atoms will have a value of 1 placed in the corresponding slot in the
            return list while atoms not selected will be assigned 0.
        r   )stderrstdoutr   z(In AmberMask.Selection(), debug active!
   zoriginal mask: ==%s==
*c             S   s   g | ]}d qS )   r   ).0atomr   r   r   
<listcomp>X   s    z'AmberMask.Selection.<locals>.<listcomp>ztokenized mask: ==%s==
zpostfix mask: ==%s==
c             S   s   g | ]}d | qS )r   r   )r   r   r   r   r   r   d   s    )sysr   r   writer   r   r
   atoms	_tokenize_torpn	_evaluate)r   prnlevr   r   r   infixpostfixr   r   r   r   :   s      
 
  zAmberMask.Selectionc             C   s  d}d}d}d}xh|t | jk rz| j| }| rD|d7 }qn,| |sj|t | jd ksj|dkr|dkr|t | jd kr|dkrd}ntd|dkr|t | jd kr|dkr||7 }|d	7 }d}||7 }d}|t | jd ks|dkr||7 }|d
krpd| }|d7 }y| j| }W n$ tk
rJ   td| j Y nX ||7 }d}| jjdkrntd|dkrptd| j n| |r|dkrd}d}|dkrtd| j |dkr|dkrd}ntd||7 }n|dkr|dkrd}d}n|d7 }d}nZ|dkrd|dkr4d}d}n.|dkrL|d7 }d}n|dkrp|d7 }d}ntd| |d7 }qW d}d}d}x|t |k r
|| }|dkr|d7 }d}nF|dkr|dk r||d  dkrtd d}n|dkr|d7 }|d7 }qW |d! S )"a   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
         r   r   )()=r   z&AmberMask: '=' not in name list syntaxr+   z]))<>z([%szBad distance syntax [%s]   Nz!<,> operators require coordinates):@zBad syntax [%s]z([z'=' not in name list syntaxr0   z([:z])|([:r1   z([@r   z]&[@z])|([@zUnknown symbol (%s) expression[]   zempty token in infix
)	lenr   isspace_isOperatorr   
IndexErrorr
   Zcoordinates
_isOperand)r   r&   bufferr'   flagr   pnr   r   r   r#   i   s    

&  















zAmberMask._tokenizec             C   s   t |dko|dkS )z* Determines if a character is an operator r   z!&|<>)r6   )r   charr   r   r   r8      s    zAmberMask._isOperatorc             C   s   t |dko|dkp| S )z) Determines if a character is an operand r   z\*/%-?,'.=+_)r6   isalnum)r   r?   r   r   r   r:      s    zAmberMask._isOperandc             C   s  d}dg}d}d}x|t |k r|| }|dkrD||7 }d}n^|dkr\||7 }d}nF|rl||7 }n6|dkr|| n |dkr| }x,|dkr|dkrtd	||7 }| }qW n|dkr| }x|dkr|dkrtd	||7 }| }qW n| |r| |}	| |t |d  }
|	|
krH|| nLx@|	|
kr| }||7 }| |}	| |t |d  }
qJW || ntd
| |d7 }qW |S )z$ Converts the infix to an RPN array r)   r5   r   r2   r   r3   r*   r+   zUnbalanced parentheses in Mask.zUnknown symbol %s)r6   appendpopr   r8   	_priority)r   r'   r&   r(   stackr<   r   r=   ZppZP1ZP2r   r   r   r$      sX    




zAmberMask._torpnc             C   s.  ddl m} d}g }d}x|t|k r|| }|dkrBd}n|dkrf|}| |}	||	 n\| |sx|dkr||7 }n>|dkrd}
d}y"| }
| }| ||
|}	W n tk
r   t	d	Y nX ||	 n|d
krj|t|d k r||d  dkr||7 }nPy | }
| }| 
|
|}	W n$ tk
r\   dd | jjD S X ||	 nX|dkry| }
W n tk
r   t	dY nX | |
}	||	 nt	d| |d7 }qW y| }	W n tk
r   t	dY nX |rt	d|dkr*|dt|	| jf  |	S )zA Evaluates a postfix in RPN format and returns a selection array r   )r   r)   r2   r3   )r0   r1   )&|NzIllegal binary operation)r-   r.   r   c             S   s   g | ]}d qS )r   r   )r   ar   r   r   r   =  s    z'AmberMask._evaluate.<locals>.<listcomp>!zIllegal ! operationz!Unknown symbol evaluating RPN: %sz$Empty stack -- no available operandsz)There may be missing operands in the mask   z%d atoms selected by %s)r    r   r6   _selectElemMaskrA   r:   rB   _binopr9   r   _selectDistdr
   r"   _negr!   sumr   )r   r(   r&   r   r;   rD   posr=   ptokenpmaskpmask1pmask2r   r   r   r%     sh     

$



zAmberMask._evaluatec             C   s   |  S )z Negates a given mask )Not)r   rR   r   r   r   rM   Z  s    zAmberMask._negc          	   C   s  t t| jj}|d dkr&dd }n&|d dkr<dd }ntd|d  |dd	 }|d d
krptd| yt|dd	 }W n, ttfk
r   td|dd	  Y nX ||9 }dd t|D }xt| jjD ]r\}}xh|D ]`}	| jj|	 }
|j	|
j	 }|j
|
j
 }|j|
j }|| ||  ||  }|||rd||< P qW qW |d dkrxR| jjD ]F}x>|jD ]4}||j dkrtx|jD ]}d||j< qW P qtW qhW |S )z, Selects atoms based on a distance criteria r   r-   c             S   s   | |k S )Nr   )xyr   r   r   <lambda>h      z(AmberMask._selectDistd.<locals>.<lambda>r.   c             S   s   | |kS )Nr   )rU   rV   r   r   r   rW   j  rX   z1Unknown comparison criteria for distance mask: %sr   Nz:@z"Bad distance criteria for mask: %szDistance must be a number: %sc             S   s   g | ]\}}|d kr|qS )r   r   )r   r   valr   r   r   r   x  s    z*AmberMask._selectDistd.<locals>.<listcomp>r0   )_maskr6   r
   r"   r   float	TypeError
ValueErrorr   ZxxZxyZxzZresiduesidx)r   rR   rS   rQ   ZcmpZdistanceZidxlistr   ZatomijZatomjZdxZdyZdzZd2resr   r   r   r   rL   `  sB    




zAmberMask._selectDistdc             C   s  d}d}d}d}d}t t| jj}d}d}	|drT|}
d}x|t|k rN|| }||7 }|	d7 }	|dkr||d  d	kr|	dkr|t|d ks||d  d
kr|}
q|
|kr|}
n| s|dkr|}
|t|d krd}	t|dkrD|	dkrD|
|kr|  n.|
|kr*| || n|
|kr@| || |}
|d7 }qDW n|dr|}d}x|t|k r|| }||7 }|	d7 }	|dkr||d  d	kr||kr |}nB| s|dkr||kr |}n|dkr|}n|dkr |}|t|d krd}	t|dkr|	dkr||krB|  nn||krZ| 	|| nV||krr| 
|| n>||kr| |dd | n||kr| |dd | |d7 }qlW n2| dkr|  n|d dkr|S td|S )z Selects an element mask r   r   r   r/   r4   r)   r0   r   \,z_?*r1   z?*_%/N)r-   r.   zMask is missing : and @)rZ   r6   r
   r"   
startswithisalpha
select_all_residue_numlist_residue_namelist_atom_numlist_atom_namelist_atom_typelist_atom_elemlistr   r   )r   rP   ZALLZNUMLISTZNAMELISTZTYPELISTZELEMLISTrQ   r;   Zbuffer_pZreslistrO   r=   Zatomlistr   r   r   rJ     s    (














zAmberMask._selectElemMaskc       	      C   s   d}d}d } }}x|t |k r|| }| r:||7 }|dksR|t |d kr|dkrrt|}| ||| nt|}| ||| d}d}n|dkrt|}d}d}| s|dkstd| |d7 }qW dS )	z% Fills a _mask based on atom numbers r)   r   rb   r   -)rb   rn   z*Unknown symbol in atom number parsing [%s]N)r6   isdigitint_atnum_selectr   )	r   instringr   r;   rO   at1at2dashr=   r   r   r   rj     s,    zAmberMask._atom_numlistnamec             C   s   d}d}x|t |k r|| }| s.|dkr6||7 }|dksN|t |d krd|krp|d  rp| || n| ||| d}| s|dkstd| |d7 }q
W d	S )
z) Fills a _mask based on atom names/types r)   r   z\*?+'-_rb   r   rn   z\,?*'+-_z-Unrecognized symbol in atom name parsing [%s]N)r6   r@   ro   rj   _atname_selectr   )r   rr   r   keyr;   rO   r=   r   r   r   rk     s    zAmberMask._atom_namelistc             C   s   | j ||dd dS )z# Fills a _mask based on atom types type)rx   N)rk   )r   r;   r   r   r   r   rl     s    zAmberMask._atom_typelistc             C   s   | j ||dd dS )z
        Fills a _mask based on atom elements. For now it will just be Atom
        names, since elements are not stored in the prmtop anywhere.
        element)rx   N)rk   )r   r;   r   r   r   r   rm     s    zAmberMask._atom_elemlistc       	      C   s   d}d}d } }}x|t |k r|| }| r:||7 }|dksR|t |d kr|dkrrt|}| ||| n<yt|}W n tk
r   tdY nX | ||| d}d}n|dkrt|}d}d}|d7 }qW dS )z( Fills a _mask based on residue numbers r)   r   rb   r   z%Bad mask: error in integer conversionrn   N)r6   ro   rp   _resnum_selectr]   r   )	r   rr   r   r;   rO   rs   rt   ru   r=   r   r   r   rh   !  s.    zAmberMask._residue_numlistc             C   s   d}d}x|t |k r|| }| s.|dkr6||7 }|dksN|t |d krd|krp|d  rp| || n| || d}| s|dkstd| |d7 }q
W d	S )
z& Fills a _mask based on residue names r)   r   )r   ?+'rn   rb   r   rn   z,?*'+-z+Unknown symbol in residue name parsing [%s]N)r6   r@   ro   rh   _resname_selectr   )r   rr   r   r;   rO   r=   r   r   r   ri   >  s    zAmberMask._residue_namelistc             C   s$   xt |d |D ]}d||< qW dS )z6 Fills a _mask array between atom numbers at1 and at2 r   N)r   )r   rs   rt   r   r   r   r   r   rq   R  s     zAmberMask._atnum_selectc             C   sB   x<t | jjD ],\}}|jjd }||kr||krd||< qW dS )z4 Fills a _mask array between residues res1 and res2 r   N)r   r
   r"   residuer^   )r   Zres1Zres2r   r   r   r`   r   r   r   r{   X  s     zAmberMask._resnum_selectc             C   s   |  rHt|d }xt| jjD ] \}}|| t||kB ||< q"W n|dkry<x6t| jjD ]&\}}|| tt| |jkB ||< q`W W q tk
r   td| Y qX n:x8t| jjD ](\}}|| tt	|t
||B ||< qW dS )z9 Fills a _mask array with all atom names of a given name r   rz   zUnknown element %sN)ro   rp   r   r
   r"   r   Zatomic_numberKeyErrorr   
_nameMatchgetattr)r   Zatnamer   rx   r   r   r   r   r   rw   `  s    &zAmberMask._atname_selectc             C   sb   x\t | jjD ]L\}}t||jjr.d||< q| r|| tt||jjd kB ||< qW dS )z< Fills a _mask array with all residue names of a given name r   N)	r   r
   r"   r   r   rv   ro   rp   r^   )r   Zresnamer   r   Zatmr   r   r   r   r  s
    
zAmberMask._resname_selectc             C   s4   |dkr| |S |dkr$||S td| dS )z, Does a binary operation on a pair of masks rE   rF   zUnknown operator [%s]N)AndOrr   )r   oprR   rS   r   r   r   rK   |  s
    

zAmberMask._binopc             C   s^   |dkrdS |dkrdS |dkr$dS |dkr0dS |d	kr<d
S |dkrHdS t d|| jf d S )N)r.   r-      )rH   r   )rE   r4   )rF   r/   )r*   r   )r5   r   z$Unknown operator [%s] in Mask ==%s==)r   r   )r   r   r   r   r   rC     s          zAmberMask._priorityN)F)r   F)rv   )rv   )__name__
__module____qualname____doc__r   r   r   r   r#   r8   r:   r$   r%   rM   rL   rJ   rj   rk   rl   rm   rh   ri   rq   r{   rw   r   rK   rC   r   r   r   r   r	      s2   


/o6@0R
	


r	   c             C   s   ddl }t| dd} t|dd}d}| d|dd|d} | d	|d
d|d
} | d|dd|d} | |kpt|| d|d S )a  
    Determines if atnam1 matches atnam2, where atnam1 can have * as a wildcard
    and spaces are ignored. atnam2 should come from the prmtop. We'll use regex
    to do this.

    We will replace * with a regex that will match any alphanumeric character
    0 or more times: * --> \w*

    We will replace ? with a regex that will match exactly 1 alphanumeric
    character: ? --> \w

    Then, we will substitute all instances of atnam2 in atnam1 with ''. If it's
    a complete match, then our result will be a blank string (and will evaluate
    to False for boolean conditions). If it's not blank, then it's not a full
    match and should return False
    r   N r)   z<!PROTECT!>z\*r   z\S*z\?r|   z\Sz\+r}   r   )restrreplaceboolsub)Zatnam1Zatnam2r   Rr   r   r   r     s    r   c               @   sX   e Zd ZdZdd Zdd Zdd Zdd	 Zd
d Zdd Z	dd Z
dd Zdd ZdS )rZ   z$ Mask array; only used by AmberMask c             C   s$   || _ t| dd t|D  d S )Nc             S   s   g | ]}d qS )r   r   )r   r   r   r   r   r     s    z"_mask.__init__.<locals>.<listcomp>)natomlistr   r   )r   r   r   r   r   r     s    z_mask.__init__c             O   s   t dd S )Nz_mask is a fixed-length array!)r   )r   argskwargsr   r   r   rA     s    z_mask.appendc             O   s   t dd S )Nz_mask is a fixed-length array!)r   )r   r   r   r   r   r   extend  s    z_mask.extendc             O   s   | d S )Nr   )r   r   r   r   r   r   rB     s    z	_mask.popc             O   s   t dd S )Nz_mask is a fixed-length array!)r   )r   r   r   r   r   r   remove  s    z_mask.removec             C   sP   | j |j krtdt| j }x,tt| D ]}t| | o@|| ||< q,W |S )Nz1_mask: and() requires another mask of equal size!)r   r   rZ   r   r6   rp   )r   othernew_maskr   r   r   r   r     s    
z	_mask.Andc             C   sP   | j |j krtdt| j }x,tt| D ]}t| | p@|| ||< q,W |S )Nz0_mask: or() requires another mask of equal size!)r   r   rZ   r   r6   rp   )r   r   r   r   r   r   r   r     s    
z_mask.Orc             C   s2   t | j}x"t| jD ]}d| |  ||< qW |S )Nr   )rZ   r   r   )r   r   r   r   r   r   rT     s    
z	_mask.Notc             C   s    xt | jD ]}d| |< qW d S )Nr   )r   r   )r   r   r   r   r   rg     s    z_mask.select_allN)r   r   r   r   r   rA   r   rB   r   r   r   rT   rg   r   r   r   r   rZ     s   rZ   N)r   Z
__future__r   r   r   
exceptionsr   Zperiodic_tabler   Zutils.six.movesr   objectr	   r   r   rZ   r   r   r   r   <module>   s        
 