B
    b                 @   s  d Z ddlmZmZmZ ddlmZ ddlZddlm	Z	 ddl
mZ ddlZddlmZ dd	lmZ dd
lmZ ddlmZmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlm Z  ddl!m"Z"m#Z#m$Z$ ddl%m&Z& ddl'Z'ddl(m)Z) ddl*m+Z+m,Z, ddlm-Z-m.Z.m/Z/ ddl0m1Z1 dZ2yddl3m4Z4 dZ2W n" e5k
rZ   ddl6m7Z4 Y nX dd Z8dd Z9e2rxe8Z:ne9Z:ddl;Z;e;<e=Z>dd  Z?e"eG d!d" d"eeZ@dS )#z
This module contains the class for storing and creating/converting/writing
OpenMM-style ffxml files defining a force field

Author(s): Jason Swails
    )absolute_importprint_functiondivision)copyN)wraps)closing   )CharmmImproperMatchingMixin)DEFAULT_ENCODING)FileFormatType)ResidueTemplatePatchTemplate)ParameterSet)Element)NoUreyBradley)unit)genopen)add_metaclassstring_types	iteritems)range)ParameterWarning)productchain)DihedralTypeImproperType	DrudeAtom)OrderedDictF)etreeT)ElementTreec             C   s   t j| tdddS )NT)encodingpretty_printzutf-8)r   tostringr
   decode)tree r%   7lib/python3.7/site-packages/parmed/openmm/parameters.py_pretty_print_lxml(   s    r'   c             C   s6   ddl m} tj|  tdd}||jddS )Nr   )minidom)r    zutf-8z  )indent)	Zxml.domr(   r   r"   Zgetrootr
   r#   ZparseStringZtoprettyxml)r$   r(   xmlr%   r%   r&   _pretty_print_xml_stdlib+   s    r+   c                s   t   fdd}|S )za
    Decorator to raise an ImportError if a function requires lxml but it is not
    present
    c                 s   t d krtd | |S )Nz(required package lxml could not be found)r   ImportError)argskwargs)funcr%   r&   wrapper=   s    zneeds_lxml.<locals>.wrapper)r   )r/   r0   r%   )r/   r&   
needs_lxml8   s    r1   c                   sB  e Zd ZdZedd Z fddZedd Zed<d
dZ	d=ddZ
ed>ddZdd Zdd Zdd Zdd Zedd Zedd Zedd Zd d! Zed?d"d#Zd$d% Zed@d&d'Zed(d) Zed*d+ Zed,d- Zed.d/ Zed0d1 Zed2d3 Zed4d5 Zed6d7 Zed8d9 Z d:d; Z!  Z"S )AOpenMMParameterSetat   Class storing parameters from an OpenMM parameter set

    Parameters
    ----------
    filenames : str, list of str, file-like, or list of file-like; optional
        Either the name of a file or a list of filenames from which parameters
        should be parsed.

    Notes
    -----
    Order is important in the list of files provided. The parameters are loaded
    in the order they are provided, and any parameters that are specified in
    multiple places are overwritten (that is, the *last* occurrence is the
    parameter type that is used)

    See Also
    --------
    :class:`parmed.parameters.ParameterSet`
    c             C   s   dS )aT  
        Identifies the file type as either an Amber-style frcmod or parm.dat
        file.

        Parameters
        ----------
        filename : str
            Name of the file to check format for

        Returns
        -------
        is_fmt : bool
            True if it is an Amber-style parameter file. False otherwise.
        Fr%   )filenamer%   r%   r&   	id_formatZ   s    zOpenMMParameterSet.id_formatc                s$   t t|   |rtdd| _d S )Nz%Cannot yet read OpenMM Parameter setsF)superr2   __init__NotImplementedErrorunique_atom_types)self	filenames)	__class__r%   r&   r6   m   s    zOpenMMParameterSet.__init__c             C   sN  xD|j D ]:}|j|jkr2td|j|j dS |j|j j|_qW tdd |j D }x0|j	D ]&}|d }d|j
||  _d|| _qbW xFt|jD ]8}|jjdks|jjdkrtd|j || qW |jdkrJxht|jD ]Z}|jjd	kr*|jjd	kr*td
|j || qtd|j|j|jjf  qW dS )aI  
        Modify non-compliant residue templates to conform with OpenMM requirements.

        * To correctly detect waters, OpenMM ffxml water models must not contain
          non-chemical bond constraints. Theses are removed when importing
          foreign parameter sets (e.g., CHARMM) into OpenMM parameter sets,
          and not restored on conversion from OpenMM to other formats

        Parameters
        ----------
        params : :class:`parmed.parameters.ParameterSet`
            ParameterSet containing the list of parameters to be converted to a
            OpenMM-compatible parameter set
        residue : :class:`parmed.modeller.Residue`
            The residue to remediate

        Returns
        -------
        missing_parameters : bool
            If True, the residue template is missing some parameters

        zPResidue {} contains atom type {} not found in parameter set and will be dropped.Fc             s   s   | ]}|j |jfV  qd S )N)nametype).0atomr%   r%   r&   	<genexpr>   s    zAOpenMMParameterSet._remediate_residue_template.<locals>.<genexpr>   r   z-Deleting bonds to virtual sites in residue {}ZH2OHz'Deleting H-H bond from water residue {}zkeeping %s to %s %sT)atomsr=   atom_types_strwarningswarnformatr<   atomic_numberdict	lonepairs
atom_typeslistbondsatom1atom2LOGGERdebugZdelete_bondZempirical_chemical_formulaZelement_name)clsparamsresiduer?   typeslonepairZlp_atombondr%   r%   r&   _remediate_residue_templates   s*     z.OpenMMParameterSet._remediate_residue_templateFTc             C   s\  |  }|rt |}|j |_|_|j|_|j|_|j|_|j|_|j|_|j|_|j	|_	|j
|_
|j|_|j|_|j|_|j|_|j|_|j|_|j|_|j|_||_t|dr|j|_|rLt }x6t|jD ](\}}t|tr| || || qW x|D ]}||j|j< qW x~t|jD ]"\}}	| ||	 |	|j|	j< q$W nLx$t|jD ]\}}||j|j< qXW x$t|jD ]\}}	|	|j|	j< q~W t  }
g }xpt|jD ]b\}}	t|	t!rt"#|	}||
kr|	|j|< |	|
|< n$|
| }t$%d&|	| ||	 qW t'|dkr>t$%d&t'|t'|j x|D ]}	|j|	j= qDW |S )a  
        Instantiates an OpenMMParameterSet from another ParameterSet (or
        subclass). The main thing this feature is responsible for is converting
        lower-case atom type names into all upper-case and decorating the name
        to ensure each atom type name is unique.

        Warning
        -------
        Converting parameter sets to OpenMM can be lossy, and can modify the
        original parameter set unless ``copy=True``:
        * To correctly detect waters, OpenMM ffxml water models must not contain
          non-chemical bond constraints. Theses are removed when importing
          foreign parameter sets (e.g., CHARMM) into OpenMM parameter sets,
          and not restored on conversion from OpenMM to other formats.

        Parameters
        ----------
        params : :class:`parmed.parameters.ParameterSet`
            ParameterSet containing the list of parameters to be converted to as
            OpenMM-compatible parameter set
        copy : bool, optional, default=False
            If True, the returned parameter set is a deep copy of ``params``. If
            False, the returned parameter set is a shallow copy. Default False.
        remediate_residues : bool, optional, default=True
            If True, will remove non-chemical bonds and drop Residue definitions
            that are missing parameters
        unique_atom_types : bool
            If True, a unique OpenMM atom type will be created for every atom of
            every residue.  In this case, the :class:`AtomType` objects correspond
            to atom classes rather than atom types (in the OpenMM terminology).

        Returns
        -------
        new_params : OpenMMParameterSet
            OpenMMParameterSet with the same parameters as that defined in the
            input parameter set
        _improper_key_mapz>Patch {} discarded because OpenMM considers it identical to {}r   z!{} patches discarded, {} retained)(_copyrK   rD   Zatom_types_intZatom_types_tuple
bond_typesangle_typesurey_bradley_typesdihedral_typesimproper_typesimproper_periodic_typesZrb_torsion_types
cmap_typesnbfix_typesZ
pair_typesZparametersetscombining_ruleZ_combining_ruledefault_sceedefault_scnbr8   hasattrrY   rL   r   residues
isinstancer   rX   appendr<   patchesr   r   r2   _templhasherrE   rF   rG   len)rR   rS   r   Zremediate_residuesr8   Z
new_paramsZremediated_residuesr<   rT   patchZunique_patchesZdiscarded_patches	templhashpatch_collisionr%   r%   r&   from_parameterset   sl    '







z$OpenMMParameterSet.from_parametersetc             C   s:   | j r*|rd|j|jf S d|j|jf S |r4|jS |jS )a  Get the OpenMM atom type for an atom.

        Parameters
        ----------
        atom : :class:`Atom`
            the atom for which to get the type
        residue : :class:`ResidueTemplate` or :class:`PatchTemplate`
            the residue the atom belongs to
        drude : bool
            if True, get the atom type for the Drude particle attached to the
            atom rather than the atom itself
        zDrude-%s-%sz%s-%s)r8   r<   
drude_typer=   )r9   r?   rT   Zdruder%   r%   r&   _get_mm_atom_type  s    z$OpenMMParameterSet._get_mm_atom_typeNdefaultc          	   C   s  |s(|   }| |}	|r4tdt nt }t }	| jrhy|   W n  tk
rf   tdt Y nX | 	|\}
}t
d x | jD ]}t
d||
|  qW |r|   |   td}| || | ||	| | j||||d | ||
 | ||	 | ||	 | ||	 | ||	| | ||	 | ||	 | ||	 | ||	| | ||	| | ||	 t|}t |}t!|t"rt#t$|d}|%| W dQ R X n
|%| dS )	aU   Write the parameter set to an XML file for use with OpenMM

        Parameters
        ----------
        dest : str or file-like
            The name of the file or the file-like object (with a ``write``
            attribute) to which the XML file will be written
        provenance : dict, optional
            If present, the XML file will be tagged with the available fields.
            Keys of the dictionary become XML etree.Element tags, the values of the
            dictionary must be instances of any of:
            - str / unicode (Py2) or str (Py3) - one XML element with this
            content is written
            - list - one XML element per each item of the list is written, all
            these XML elements use the same tag (key in provenance dict)
            - dict - one of the keys of this dict must be the same as the key of
            of the provenance dict under which this dict is nested. The value
            under this key becomes the content of the XML element. Remaining keys
            and their values are used to construct attributes of the XML element.
            Note that OrderedDict's should be used to ensure appropriate order
            of the XML elements and their attributes.
            Default is no provenance.
            Example (unordered):
            provenance = {'Reference' : ['Nature', 'Cell'],
                          'Source' : {'Source': 'leaprc.ff14SB', sourcePackage :
                          'AmberTools', sourcePackageVersion : '15'},
                          'User' : 'Mark'}
        write_unused : bool
            If False: a) residue templates using unavailable atom types will not
            be written, b) atom types that are not used in any of the residue
            templates remaining and parameters including those atom types will
            not be written. A ParameterWarning is issued if any such residues are
            found in a).
        separate_ljforce : bool
            If True will use a separate LennardJonesForce to create a
            CostumNonbondedForce to compute L-J interactions. It will set sigma
            to 1 and epsilon to 0 in the NonbondedForce so that the
            NonbondedForce  only calculates the electrostatic contribution. It
            should be set to True when converting a CHARMM force field file that
            doesn't have pair-specific  L-J modifications (NBFIX in CHARMM) so
            that the ffxml conversion is compatible with the main charmm36.xml file.
            Note:
            ----
            When pair-specific L-J modifications are present (NBFIX in CHARMM), this
            behavior is always present and this flag is ignored.
        improper_dihedrals_ordering : str
            The ordering to use when assigning improper torsions in OpenMM.  Default is 'default',
            other option is 'amber'
        charmm_imp: bool
            If True, will check for existence of IMPR in each residue and patch template,
            and write out the explicit improper definition without wildcards in the ffxml file.
        skip_duplicates : bool
            If True: residues which appear identical to an existing residue will
            not be written. This is usually the best choice. The most common
            reason for setting skip_duplicates to false is if you have different
            parametrizations for stereoisomers. Note that since OpenMM's residue
            hashing is not aware of chirality, if you wish to use the results in
            simulations you will need to explicitly provide the template names
            for affected residues.

        Notes
        -----
        The generated XML file will have the XML tag ``DateGenerated`` added to
        the provenance information set to the current date. Therefore, you
        should not provide this information in ``provenance`` (it will be
        removed if it is provided).
        zSome residue templates using unavailable AtomTypes were found. They will not be written to the ffxml as write_unused is set to Falsez6Some residue templates are using unavailable AtomTypeszValid patch combinations:z%8s : %sZ
ForceField)valid_patches_for_residuewN)&_find_unused_residues_find_unused_typesrE   rF   r   setrK   Ztypeify_templatesKeyError#_determine_valid_patch_combinationsrP   rQ   rj   _find_explicit_impropers_compress_impropersr   r   _write_omm_provenance_write_omm_atom_types_write_omm_residues_write_omm_patches_write_omm_bonds_write_omm_angles_write_omm_urey_bradley_write_omm_dihedrals_write_omm_impropers_write_omm_cmaps_write_omm_scripts_write_omm_nonbonded_write_omm_LennardJonesForce_write_omm_DrudeForcer   r!   rh   r   r   r   write)r9   dest
provenanceZwrite_unusedseparate_ljforceimproper_dihedrals_orderingZ
charmm_impskip_duplicatesskip_residues
skip_typesvalid_residues_for_patchrt   
patch_namerootr$   r*   fr%   r%   r&   r   0  sV    G




zOpenMMParameterSet.writec       	   
      sh  t  _x$j D ]}|jtt|< qW t  }t  }fddjD  fddjD  fddxttj	tj
D ]\}xjD ]}yfdd|D }W n tk
r   wY nX d}xht| D ]\}j| }|dkrqt|tr|||< d	}qt|tr&|||< d	}qtd
||qW |std|||qW qW |_|_dS )a{  
        For every residue, find any explicitly-specified (e.g. CHARMM) improper torsions and identify all wild-card improper parameters that match.
        Expand all of these out into explicit impropers.
        This is necessary for OpenMM to correctly handle impropers for these residues.

        .. todo ::

           * Do we need to do this for patches as well?

        c                s    g | ]} j | jd kr|qS )   )rK   rH   )r>   t)r9   r%   r&   
<listcomp>  s    z?OpenMMParameterSet._find_explicit_impropers.<locals>.<listcomp>c                s    g | ]} j | jd kr|qS )   )rK   rH   )r>   r   )r9   r%   r&   r     s    c                sf   dd | j D }dd | j D }|dkr, S |dkr8S |d dkrRtd| n||| gS d	S )
zHReturn list of atom type(s) that match the given atom name.
            c             S   s   g | ]
}|j qS r%   )r<   )r>   ar%   r%   r&   r     s    zROpenMMParameterSet._find_explicit_impropers.<locals>.get_types.<locals>.<listcomp>c             S   s   g | ]
}|j qS r%   )r=   )r>   r   r%   r%   r&   r     s    z-Cz+Nr   )-+zUnknown atom name %sN)rC   
ValueErrorindex)rT   atomnameZa_namesZa_types)C_typesN_typesr%   r&   	get_types  s    z>OpenMMParameterSet._find_explicit_impropers.<locals>.get_typesc                s   g | ]} |qS r%   r%   )r>   r   )r   rT   r%   r&   r     s    FNTzPSomething went wrong with improper type for {} returning an unexpected object {}z?No improper found for improper {} in residue {} (types were {}))r   rY   r_   keystuplesortedrK   r   r   rg   rj   Z_imprr   r   Zmatch_improper_typerh   r   r   	ExceptionrG   r`   )	r9   keyZimproper_harmonicZimproper_periodicr<   ZimprrU   Zimproper_foundimproperr%   )r   r   r   rT   r9   r&   r{     s<     
z+OpenMMParameterSet._find_explicit_impropersc             C   s   | j s
dS t }t }xnt| j D ]`\}}tt|}||krr|| }||  j|j7  _td|||  q"|||< |||< q"W || _ dS )z
        OpenMM's ForceField cannot handle impropers that match the same four atoms
        in more than one order, so Peter Eastman wants us to compress duplicates
        and increment the spring constant accordingly.

        Nz<Compressing improper {} because it contains same atoms as {})	r_   r   r   r   r   psi_krE   rF   rG   )r9   Zunique_keysr_   rC   r   Z
unique_keyZatoms2r%   r%   r&   r|     s     z&OpenMMParameterSet._compress_impropersc                sD   t  }x8t jD ]*\}}t fdd|jD r|| qW |S )Nc             3   s   | ]}|j  jkV  qd S )N)r=   rK   )r>   r?   )r9   r%   r&   r@     s    z;OpenMMParameterSet._find_unused_residues.<locals>.<genexpr>)rx   r   rg   anyrC   add)r9   r   r<   rT   r%   )r9   r&   rv     s
    z(OpenMMParameterSet._find_unused_residuesc                sV   t   x:t| jD ],\}}||krx|jD ]} |j q*W qW  fdd| jD S )Nc                s   h | ]}| kr|qS r%   r%   )r>   typ)
keep_typesr%   r&   	<setcomp>  s    z8OpenMMParameterSet._find_unused_types.<locals>.<setcomp>)rx   r   rg   rC   r   r=   rK   )r9   r   r<   rT   r?   r%   )r   r&   rw     s    z%OpenMMParameterSet._find_unused_typesc             C   s   t  }t| jdkr0|t tdd | jD 7 }t| drdt| jdkrd|t tdd | jD 7 }t| jdkr|t tdd | jD 7 }| jr|| jjf7 }| j	r|| j	jf7 }t
|S )zz
        Create a unique hash for each residue and patch template using only properties rendered to OpenMM ffxml.
        r   c             S   s   g | ]}|j t|jfqS r%   )r=   strcharge)r>   r?   r%   r%   r&   r   %  s    z3OpenMMParameterSet._templhasher.<locals>.<listcomp>delete_atomsc             S   s   g | ]}|qS r%   r%   )r>   	atom_namer%   r%   r&   r   (  s    c             S   s<   g | ]4}|j j|jjk r(|j j|jjfn|jj|j jfqS r%   )rN   r<   rO   )r>   rW   r%   r%   r&   r   +  s    )r   rl   rC   r   rf   r   rM   headr<   tailhash)rT   	hash_infor%   r%   r&   rk     s    zOpenMMParameterSet._templhasherc       
         s   t |d}t |d}dtj  d d  |_|p<t }xt|D ]\ } dkrZqHt|t	sj|g}x|D ]}t|t
rt  }||_|| qpt|tst|tr |krtd fdd|D }|  }	dd	 | D }t j| f|}t|	|_qptd
  qpW qHW d S )NZInfoZDateGeneratedz%02d-%02d-%02d   zAContent of an attribute-containing element specified incorrectly.c                s   g | ]}| kr|qS r%   r%   )r>   r   )tagr%   r&   r   I  s    z<OpenMMParameterSet._write_omm_provenance.<locals>.<listcomp>c             S   s   i | ]\}}t ||qS r%   )r   )r>   kvr%   r%   r&   
<dictcomp>K  s    z<OpenMMParameterSet._write_omm_provenance.<locals>.<dictcomp>z(Incorrect type of the %s element content)r   
SubElementdatetimeZnowZ	timetupletextr   r   rh   rL   r   r   ri   rI   ry   itemsr   	TypeError)
r9   r   r   infoZdate_generatedZcontentZsub_contentitem
attributesZelement_contentr%   )r   r&   r}   4  s.    
 



z(OpenMMParameterSet._write_omm_provenancec             C   sx  | j s
d S t|d}| jrxt| j t| j  D ]}|j|krJq:x|j	D ]}| j |j
 }| |||j
t|jd}|jdkrtt|j |d< tj|df| t|trR| ||d|jdd}tj|df| qRW q:W nxt| j D ]~\}	}|	|krq|jdkstd|	|	t|jd}|jdkrJtj|df| qt|j }
tj|dfdt|
i| qW d S )	NZ	AtomTypes)r<   classmassr   elementZTypeTz0.0zAtomic number not set!)rK   r   r   r8   rL   rg   valuesrj   r<   rC   r=   rr   r   r   rH   r   rh   r   rq   r   AssertionError)r9   xml_rootr   r   xml_sectionrT   r?   	atom_typeZ
propertiesr<   r   r%   r%   r&   r~   Q  s2     "
 


 
z(OpenMMParameterSet._write_omm_atom_typesc             C   s  |\}}}}}}}}	|dkr(dddg}
n |dkr<dddg}
nt d| |d }|tjd	 9 }d
|	 tj d	 }	|t| |t| t|	 |t| t|	 g}dd |D }td||||dddt|
d t|
d t|
d dddt|d t|d t|d dS )NZrelativeg      g        g      ?Zbisectorg      ?zUnknown lonepair type: g      $@g     f@   c             S   s    g | ]}t |d kr|ndqS )g|=r   )abs)r>   xr%   r%   r&   r   x  s    z?OpenMMParameterSet._get_lonepair_parameters.<locals>.<listcomp>ZlocalCoords10r   rA   r   z-1)r=   ZsiteName	atomName1	atomName2Z	atomName3Zwo1Zwo2Zwo3Zwx1Zwx2Zwx3Zwy1Zwy2Zwy3Zp1Zp2Zp3)r   mathZpiZcosZsinrI   r   )r9   rV   Zlptypea1a2a3a4rZthetaZphiZxweightspr%   r%   r&   _get_lonepair_parametersl  s"    <z+OpenMMParameterSet._get_lonepair_parametersc             C   sd  | j s
d S |d krt }t }t|d}x2t| j D ]"\}}||krLq8t|}	|	|kr||	 }
|rtd	||
 q8ntd	||
 |||	< |j
dkrtj|d|jd}ntj|d|jt|j
d}x|jD ]}t|tr@tj|d|j| ||t|j|j d	 tj|dd
|j | ||dt|jd	 qtj|d|j| ||t|jd	 qW x*|jD ] }tj|d|jj|jjd qpW x$|jD ]}t|d| | qW x"|jD ]}tj|d|jd qW |jd k	rtj|d|jjd |jd k	r,|j|jk	r,tj|d|jjd |j|kr8x$||j D ]}tj|d|d qBW q8W d S )NZResidueszJSkipping writing of residue {} because OpenMM considers it identical to {}z>Residue {} will be considered by OpenMM to be identical to {}.r   ZResidue)r<   )r<   overrideAtom)r<   r=   r   DTBond)r   r   VirtualSiteZExternalBond)atomNameZ
AllowPatch)rg   r   r   r   r   r2   rk   rE   rF   rG   override_levelr<   r   rC   rh   r   rr   r   drude_chargerM   rN   rO   rJ   r   Zconnectionsr   r   )r9   r   r   r   rt   Zwritten_residuesr   r<   rT   rn   Zresidue_collisionZxml_residuer?   rW   rV   r   r%   r%   r&   r     sL      

*,( 
z&OpenMMParameterSet._write_omm_residuesc                s   t  }x| j D ] t | j< qW t  }x| j D ]}t ||j< q8W fdd| j D }xd| j D ]V  fdd|D }x>t||D ]0\}}|r| j |j ||j  j qW qpW ||gS )a,  
        Determine valid (permissible) combinations of patches with residues that
        lead to integral net charges.

        Parameters
        ----------
        skip_residues : set of ResidueTemplate
            List of residues to skip

        Returns
        -------
        valid_residues_for_patch : dict
            valid_residues_for_patch[patch] is a list of residues compatible with that patch
        valid_patches_for_residue : dict
            valid_patches_for_residue[residue] is a list of patches compatible with that residue

        c                s   g | ]}| kr|qS r%   r%   )r>   rT   )r   r%   r&   r     s    zJOpenMMParameterSet._determine_valid_patch_combinations.<locals>.<listcomp>c                s   g | ]}|  qS r%   )Zpatch_is_compatible)r>   rT   )rm   r%   r&   r     s    )r   rj   r   rL   r<   rg   zipri   )r9   r   r   rt   rT   rg   Zresidue_compatibilitiesZis_compatibler%   )rm   r   r&   rz     s    z6OpenMMParameterSet._determine_valid_patch_combinationsc                s  | j s
dS t }t|d}xt| j D ]\}}||ks*t|| dkrNq*t|}||kr||| }	t	d
||	 q*|||< |jdkrtj|d|jd}
ntj|d|jt|jd}
i }x|| D ]}y| j| }W n` tk
r@ } z@d}|d	| 7 }|d
||  7 }|d| 7 }|t|7 }|W dd}~X Y nX ||}g }x|jD ]}|j|krnd}nd}t|tr||t|j| ||t|j|j df ||td|j | ||dt|jdf n(||t|j| ||t|jdf qXW x$|jD ]}|dt|df qW x.|jD ]$}|dt|jj|jjdf q8W xf|jD ]\}|jj|ks|jj|krh|jjdks|jjdkrh|dt|jj|jjdf qhW xJ|jD ]@}|jj|ks|jj|kr|dt|jj|jjdf qW |jdk	rD|jdkrD|dt|jjdf |jdk	rt|jdkrt|dt|jjdf |jdkr|jdk	r|dt|jjdf |jdkr|jdk	r|dt|jjdf x$|j D ]}|d| !|f qW |r*x(||j D ]}|dt|df qW t"dd |D }||krX||  d7  < qd||< qW t#|$   fdd|% D d }x$|D ]\}}t|
|t| qW q*W dS )a  
        Write patch definitions for OpenMM ForceField

        Parameters
        ----------
        xml_root : lxml.etree.Element
            The XML Element write the <Patches> section to.
        valid_residues_for_patch : dict of str : str
            valid_residues_for_patch[patch_name] lists the residue names valid for this patch
        write_apply_to_residue : bool, optional, default=False
            If True, will write <ApplyToResidue> tags.

        NZPatchesr   zHSkipping writing of patch {} because OpenMM considers it identical to {}ZPatch)r<   )r<   r   z.Compatible residue not found in self.residues
z   patch name: %s
z    valid patch combinations: %s
z   residue name: %s
ZAddAtomZ
ChangeAtom)r<   r=   r   r   TZ
RemoveAtomZ
RemoveBond)r   r   ZAddBondZRemoveExternalBond)r   ZAddExternalBondr   ZApplyToResiduec             s   s0   | ](}|d  t dd |d  D fV  qdS )r   c             s   s   | ]
}|V  qd S )Nr%   )r>   r   r%   r%   r&   r@   1  s    zBOpenMMParameterSet._write_omm_patches.<locals>.<genexpr>.<genexpr>rA   N)r   r   )r>   ir%   r%   r&   r@   1  s    z8OpenMMParameterSet._write_omm_patches.<locals>.<genexpr>rA   c                s   g | ]\}}| kr|qS r%   r%   )r>   r   value)	max_countr%   r&   r   9  s    z9OpenMMParameterSet._write_omm_patches.<locals>.<listcomp>)&rj   r   r   r   r   rl   r2   rk   rE   rF   rG   r   r<   r   rg   ry   Zapply_patchrC   rh   r   ri   rI   rr   r   r   r   rM   rN   rO   rH   r   r   rJ   r   r   maxr   r   )r9   r   r   Zwrite_apply_to_residueZwritten_patchesZxml_patchesr<   rm   rn   ro   Z	patch_xmlZversionsZresidue_namerT   emsgZpatched_residueZinstructionsr?   Zcommandr   rW   rV   Zattribr%   )r   r&   r     s     


.0.$$$
z%OpenMMParameterSet._write_omm_patchesc       
   
      s   | j s
d S t|d}t }tjtj}tjtj	|d  d }xt
| j D ]|\\}}}	t fdd||fD rxqP||f|krqP|||f |||f tj|d||t|	j| t|	j| d qPW d S )NZHarmonicBondForcer   c             3   s   | ]}| kV  qd S )Nr%   )r>   r   )r   r%   r&   r@   E  s    z6OpenMMParameterSet._write_omm_bonds.<locals>.<genexpr>r   )class1class2lengthr   )r[   r   r   rx   u	angstromsconversion_factor_to
nanometerskilocalorie	kilojouler   r   r   r   reqr   )
r9   r   r   	xml_forceZ
bonds_doneZlconvkconvr   r   rW   r%   )r   r&   r   =  s       z#OpenMMParameterSet._write_omm_bondsc                s   | j s
d S t|d}t }tjtj}tjtj	d }xt
| j D ]\\}}}	}
t fdd|||	fD rtqH|||	f|krqH||||	f ||	||f tj|d|||	t|
j| t|
j| d qHW d S )NZHarmonicAngleForcer   c             3   s   | ]}| kV  qd S )Nr%   )r>   r   )r   r%   r&   r@   S  s    z7OpenMMParameterSet._write_omm_angles.<locals>.<genexpr>ZAngle)r   r   class3angler   )r\   r   r   rx   r   degreer   radiansr   r   r   r   r   r   Ztheteqr   )r9   r   r   r   Zangles_donetconvr   r   r   r   r   r%   )r   r&   r   K  s       z$OpenMMParameterSet._write_omm_anglesc                s<  | j s| jsd S |dkr&t|d}ntj|d|d}t }tjtj}tj	tj
}dd }xt| j D ]\\}	}
}}}t fdd|	|
||fD rql|	|
||f|krql||	|
||f ||||
|	f t }xZt|D ]N\}}|d7 }t|j|d	| < t|j| |d
| < t|j| |d| < qW tj|df||	|
|||d| qlW xt| jD ]\\}
}}	}}t fdd|	|
||fD rqj|dkr|
dkr||
 }
}n|dkr|| }}|
dkr|dkr||
 }
}tj|d|	||
||||t|jt|j| t|j| d	 qjW d S )Nrs   ZPeriodicTorsionForce)Zorderingc             S   s   | dkr| S dS )NX r%   )r<   r%   r%   r&   nowildf  s    z7OpenMMParameterSet._write_omm_dihedrals.<locals>.nowildc             3   s   | ]}| kV  qd S )Nr%   )r>   r   )r   r%   r&   r@   i  s    z:OpenMMParameterSet._write_omm_dihedrals.<locals>.<genexpr>rA   zperiodicity%dzphase%dzk%dZProper)r   r   r   class4c             3   s   | ]}| kV  qd S )Nr%   )r>   r   )r   r%   r&   r@   z  s    r   Improper)r   r   r   r   Zperiodicity1Zphase1Zk1)r^   r`   r   r   rx   r   r   r   r   r   r   r   r   r   r   	enumerater   ZperZphaseZphi_k)r9   r   r   r   r   Zdiheds_doneZpconvr   r   r   r   r   r   ZdihedZtermsr   Ztermimpropr%   )r   r&   r   Y  sF       *  




z'OpenMMParameterSet._write_omm_dihedralsc                s   | j s
d S tj|ddd}tj|ddd tj|ddd tjtj}tjtj}dd	 }x|t	| j D ]n\\}}}	}
}t
 fd
d|||	|
fD rqjtj|d||||||	||
t|j| t|j| d qjW d S )NZCustomTorsionForcezk*(theta-theta0)^2)ZenergyZPerTorsionParameterr   )r<   theta0c             S   s   | dkr| S dS )Nr   r   r%   )r<   r%   r%   r&   r     s    z7OpenMMParameterSet._write_omm_impropers.<locals>.nowildc             3   s   | ]}| kV  qd S )Nr%   )r>   r   )r   r%   r&   r@     s    z:OpenMMParameterSet._write_omm_impropers.<locals>.<genexpr>r   )r   r   r   r   r   r  )r_   r   r   r   r   r   r   r   Zradianr   r   r   r   Zpsi_eq)r9   r   r   r   r   r   r   r   r   r   r   r  r%   )r   r&   r     s       z'OpenMMParameterSet._write_omm_impropersc                s   | j s
d S |td t|d}tjtj}tj	tj
d  }tjtjd  }||}t }xt| j D ]r\\}	}
}}t fdd|	|
|fD rqp|	|
|f|krqp|tkrqptj|d|	|
|t|j| t|j| d qpW d S )NzUrey-Bradley termsZAmoebaUreyBradleyForcer   c             3   s   | ]}| kV  qd S )Nr%   )r>   r   )r   r%   r&   r@     s    z=OpenMMParameterSet._write_omm_urey_bradley.<locals>.<genexpr>ZUreyBradley)r   r   r   dr   )r]   ri   r   Commentr   r   r   r   r   Zkilocalorie_per_moleangstromZkilojoule_per_mole	nanometerrx   r   r   r   r   r   r   )r9   r   r   r   length_convZ_ambfrcZ_ommfrcZfrc_convZ
ureys_doner   r   r   Zureyr%   )r   r&   r     s"     
   z*OpenMMParameterSet._write_omm_urey_bradleyc                s  | j s
d S t|d}t }d}tjtj}xt| j D ]\}}t	||krPq:||t	|< |d7 }t|d}	|j
 j}
d}xPt|jD ]B}||j }x*t|jD ]}|d|
||  |  7 }qW |d7 }qW ||	_q:W t }xt| j D ]\\}}}}}}}}}t fdd	|||||fD r*q|||||f|kr@q||||||f ||||||f tj|d
t|t	| |||||d qW d S )NZCMAPTorsionForcer   rA   ZMapr   z %s
c             3   s   | ]}| kV  qd S )Nr%   )r>   r   )r   r%   r&   r@     s    z6OpenMMParameterSet._write_omm_cmaps.<locals>.<genexpr>ZTorsion)mapr   r   r   r   Zclass5)ra   r   r   r   r   r   r   r   r   idgridZswitch_rangeTr   Z
resolutionr   rx   r   r   r   )r9   r   r   r   mapsZcounterZeconv_ZcmapZxml_mapr  Z
map_stringr   basejZused_torsionsr   r   r   r   Za5r%   )r   r&   r     s<      

$"  z#OpenMMParameterSet._write_omm_cmapsc             C   s0  | j s
d S tjtj}tjtj}t t  }}xJ| jD ]@}| j| }	x0|	D ](}
|
j	rf|
|
j	 |
jrP|
|
j qPW q<W t|dkrtdddd |D  t|dkrtdddd |D  t|dkrd	|  }n
d	| j }t|dkrd	|  }n
d	| j }tj|d
t|t|d}tj|ddd xt| j D ]\}}||krfqP|jd k	r|jd k	r|j| }|j| }nd	}d}| js|rd	}d}n$|j|jks|j|jkrtd|dkr|dkrd	}ntd| |t|tt|d}tj|df| qPW d S )NrA   zLCannot currently handle mixed 1-4 scaling: Elec. Scaling factors %s detectedz, c             S   s   g | ]}t |qS r%   )r   )r>   r   r%   r%   r&   r     s    z;OpenMMParameterSet._write_omm_nonbonded.<locals>.<listcomp>zJCannot currently handle mixed 1-4 scaling: L-J Scaling factors %s detectedc             S   s   g | ]}t |qS r%   )r   )r>   r   r%   r%   r&   r     s    r   g      ?ZNonbondedForce)coulomb14scale	lj14scaleZUseAttributeFromResiduer   )r<   g        zrOpenMM <NonbondedForce> cannot handle distinct 1-4 sigma and epsilon parameters; use separate_ljforce=True insteadz/For atom type '%s', sigma = 0 but epsilon != 0.)r   sigmaepsilonr   )rK   r   r  r   r  kilocalories
kilojoulesrx   r^   sceer   scnbrl   r7   joinpoprd   re   r   r   r   r   rminr  r  rb   rmin_14
epsilon_14r   r   )r9   r   r   r   r  ene_convr  r  r   dtr   r  r  r   r<   r   r  r  r   r%   r%   r&   r     s\     

  


 


z'OpenMMParameterSet._write_omm_nonbondedc          	   C   sh  | j s|sd S tjtj}tjtj}t }x8| jD ].}| j| }x|D ]}	|	j	rL|
|	j	 qLW q8W t|dkrtdddd |D  t|dkrd|  }
n
d| j }
tj|dt|
d	}x6t| jD ]&\}}||krq|jd k	r|jd k	r|j| }|j| }nd}d
}|d
krH|d
kr<d}ntd| |j|jksd|j|jkr|j| }|j| }|d
kr|d
krd}ntd| nd }d }|t|tt|d}|d k	rtt||d< |d k	rt||d< tj|df| qW x^t| j D ]P\}}|d | }|d | }|d }tj|d|d |d t|t|d qW d S )NrA   zJCannot currently handle mixed 1-4 scaling: L-J Scaling factors %s detectedz, c             S   s   g | ]}t |qS r%   )r   )r>   r   r%   r%   r&   r     s    zCOpenMMParameterSet._write_omm_LennardJonesForce.<locals>.<listcomp>r   g      ?ZLennardJonesForce)r  g        z/For atom type '%s', sigma = 0 but epsilon != 0.z5For atom type '%s', sigma_14 = 0 but epsilon_14 != 0.)r   r  r  	epsilon14sigma14r   gÚ?Z	NBFixPair)r   r   r  r  )rb   r   r  r   r  r  r  rx   r^   r  r   rl   r7   r  r  re   r   r   r   r   rK   r  r  r  r   r  r  Zsigma_14r   )r9   r   r   r   r  r  r  r   r   r   r  r   r<   r   r  r  r"  r!  r   rK   r   Zeminr  r%   r%   r&   r     sf    
 

 
 









z/OpenMMParameterSet._write_omm_LennardJonesForcec       
      C   sV  g }xLt | j t | j  D ],}x&|jD ]}t|tr.|||f q.W q"W t|dkrbd S | j	spt
dt|d}dtj tj d }x|D ]\}}| ||d| ||t|jtt||j t|jd}|jd k	r>|j}	| |	j||d< | |	j||d	< | |	j||d
< t|	j|d< t|	j|d< tj|df| qW d S )Nr   z)Drude particles require unique_atom_typesZ
DrudeForcerA   r   T)Ztype1Ztype2r   ZpolarizabilitytholeZtype3Ztype4Ztype5Zaniso12Zaniso34ZParticle)rL   rg   r   rj   rC   rh   r   ri   rl   r8   r   r   r   r   r  r   rr   r   r   r   Zalphar#  Z
anisotropyrO   Zatom3Zatom4Za11Za22)
r9   r   r   Zdrude_atomsrT   r?   r   Zalpha_scaler   Zanisor%   r%   r&   r   O  s.    "
z(OpenMMParameterSet._write_omm_DrudeForcec             C   s   | j dkrtdd S )NZ	geometricz1Geometric combining rule not currently supported.)rc   r7   )r9   r   r   r%   r%   r&   r   j  s    
z%OpenMMParameterSet._write_omm_scripts)FTF)F)NTFrs   FT)N)F)#__name__
__module____qualname____doc__staticmethodr4   r6   classmethodrX   rp   rr   r1   r   r{   r|   rv   rw   rk   r}   r~   r   r   rz   r   r   r   r   r   r   r   r   r   r   r   __classcell__r%   r%   )r;   r&   r2   D   sD   ;l
  {E+'i.DHr2   )Ar'  Z
__future__r   r   r   r   rZ   r   	functoolsr   
contextlibr   r   Zcharmm.parametersr	   Z	constantsr
   Zformats.registryr   Zmodeller.residuer   r   Z
parametersr   Zperiodic_tabler   Ztopologyobjectsr   r   r   r   Zutils.ior   Z	utils.sixr   r   r   Zutils.six.movesr   rE   
exceptionsr   	itertoolsr   r   r   r   r   collectionsr   Z
_have_lxmlZlxmlr   r,   Z	xml.etreer   r'   r+   r!   ZloggingZ	getLoggerr$  rP   r1   r2   r%   r%   r%   r&   <module>   sL   
