B
    b|                 @   s   d Z ddlmZmZmZ dZdZddlmZm	Z	 ddl
mZ ddlZddlZdd	lT dd
lmZmZmZ G dd deZe	re`dd Zdd ZedkrddlZddlZeeje  dS )a*  
Module simtk.unit.quantity

Physical quantities with units, intended to produce similar functionality
to Boost.Units package in C++ (but with a runtime cost).
Uses similar API as Scientific.Physics.PhysicalQuantities
but different internals to satisfy our local requirements.
In particular, there is no underlying set of 'canonical' base
units, whereas in Scientific.Physics.PhysicalQuantities all
units are secretly in terms of SI units.  Also, it is easier
to add new fundamental dimensions to simtk.dimensions.  You
might want to make new dimensions for, say, "currency" or
"information".

Some features of this implementation:
  * Quantities are a combination of a value and a unit.  The value
    part can be any python type, including numbers, lists, numpy
    arrays, and anything else.  The unit part must be a simtk.unit.Unit.
  * Operations like adding incompatible units raises an error.
  * Multiplying or dividing units/quantities creates new units.
  * Users can create new Units and Dimensions, but most of the useful
    ones are predefined.
  * Conversion factors between units are applied transitively, so all
    possible conversions are available.
  * I want dimensioned Quantities that are compatible with numpy arrays,
    but do not necessarily require the python numpy package. In other
    words, Quantities can be based on either numpy arrays or on built in
    python types.
  * Units are NOT necessarily stored in terms of SI units internally.
    This is very important for me, because one important application
    area for us is at the molecular scale. Using SI units internally
    can lead to exponent overflow in commonly used molecular force
    calculations. Internally, all unit systems are equally fundamental
    in SimTK.

Two possible enhancements that have not been implemented are
  1) Include uncertainties with propagation of errors
  2) Incorporate offsets for celsius <-> kelvin conversion



This is part of the OpenMM molecular simulation toolkit originating from
Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org.

Portions copyright (c) 2012 Stanford University and the Authors.
Authors: Christopher M. Bruns
Contributors: Peter Eastman

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
    )divisionprint_functionabsolute_importzChristopher M. Brunsz0.5)string_typesPY3)rangeN   )*)Unitis_unitdimensionlessc               @   s   e Zd ZdZdZd{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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i Zd|d'd(Zd)d* Zd+d, Zd-d. ZeZd/d0 ZeZd1d2 Zd3d4 Zd5d6 Z d7d8 Z!d9d: Z"d;d< Z#d=d> Z$d}d@dAZ%dBdC Z&dDdE Z'dFdG Z(dHdI Z)dJdK Z*dLdM Z+dNdO Z,dPdQ Z-dRdS Z.dTdU Z/dVdW Z0dXdY Z1dZd[ Z2d~d]d^Z3d_d` Z4dadb Z5dcdd Z6dedf Z7dgdh Z8didj Z9dkdl Z:dmdn Z;dodp Z<dqdr Z=dsdt Z>dudv Z?dwdx Z@dydz ZAdS )Quantitya  Physical quantity, such as 1.3 meters per second.

    Quantities contain both a value, such as 1.3; and a unit,
    such as 'meters per second'.

    Supported value types include:
      1 - numbers (float, int, long)
      2 - lists of numbers, e.g. [1,2,3]
      3 - tuples of numbers, e.g. (1,2,3)
            Note - unit conversions will cause tuples to be converted to lists
      4 - lists of tuples of numbers, lists of lists of ... etc. of numbers
      5 - numpy.arrays
    c   Nc       
      C   s  |dkrXt |r|}g }n:t|r6|j}|j}n"t|trHt}nd}yt|}W n tk
rp   d}Y nX |rTt	|dk rt}nJt
t|}yt||k}W n tk
r   d}Y nX |rt}n
t|j}tg |}x|D ]}|t| qW y4ddl}	t||	jr |	|j}n||j}W n" tk
rP   ||j}Y nX nt}t|rr||j }|j}|dkrg }|| _|| _dS )z
        Create a new Quantity from a value and a unit.

        Parameters
         - value: (any type, usually a number) Measure of this quantity
         - unit: (Unit) the physical unit, e.g. simtk.unit.meters.
        NTFr   r   )r   is_quantityunit_value
isinstancer   r   iter	TypeErrorlennextbool
ValueErrorr   appendnumpyZndarrayZarray	__class__ImportError)
selfvaluer   Zis_containeriZ
first_itemZisstrZnew_containeritemr    r!   3lib/python3.7/site-packages/parmed/unit/quantity.py__init__c   sV    	










zQuantity.__init__c             C   s   t  }| j|d< | j|d< |S )Nr   r   )dictr   r   )r   stater!   r!   r"   __getstate__   s    

zQuantity.__getstate__c             C   s   |d | _ |d | _d S )Nr   r   )r   r   )r   r%   r!   r!   r"   __setstate__   s    

zQuantity.__setstate__c             C   s   t t| j| jS )z
        Shallow copy produces a new Quantity with the shallow copy of value and the same unit.
        Because we want copy operations to work just the same way they would on the underlying value.
        )r   copyr   r   )r   r!   r!   r"   __copy__   s    zQuantity.__copy__c             C   s   t t| j|| jS )z
        Deep copy produces a new Quantity with a deep copy of the value, and the same unit.
        Because we want copy operations to work just the same way they would on the underlying value.
        )r   r(   deepcopyr   r   )r   memor!   r!   r"   __deepcopy__   s    zQuantity.__deepcopy__c             C   s   t | j|}|S )zU
        Delegate unrecognized attribute calls to the underlying value type.
        )getattrr   )r   Z	attributeZret_valr!   r!   r"   __getattr__   s    zQuantity.__getattr__c             C   s   t | jd t | j  S )zPrintable string version of this Quantity.

        Returns a string consisting of quantity number followed by unit abbreviation.
         )strr   r   
get_symbol)r   r!   r!   r"   __str__   s    zQuantity.__str__c             C   s&   t jd t| j d t| j d S )z	
        z(value=z, unit=))r   __name__reprr   r0   r   )r   r!   r!   r"   __repr__   s    zQuantity.__repr__c             C   s   || j  d t| j  S )Nr/   )r   r0   r   r1   )r   format_specr!   r!   r"   format   s    zQuantity.formatc             C   sD   | j |j s"td| j |j f | j|| j  }| j }t||S )a=  Add two Quantities.

        Only Quantities with the same dimensions (e.g. length)
        can be added.  Raises TypeError otherwise.

        Parameters
         - self: left hand member of sum
         - other: right hand member of sum

        Returns a new Quantity that is the sum of the two arguments.
        z@Cannot add two quantities with incompatible units "%s" and "%s".)r   is_compatibler   r   value_in_unitr   )r   otherr   r   r!   r!   r"   __add__   s
    zQuantity.__add__c             C   sD   | j |j s"td| j |j f | j|| j  }| j }t||S )a\  Subtract two Quantities.

        Only Quantities with the same dimensions (e.g. length)
        can be subtracted.  Raises TypeError otherwise.

        Parameters
         - self: left hand member (a) of a - b.
         - other: right hand member (b) of a - b.

        Returns a new Quantity that is the difference of the two arguments.
        zECannot subtract two quantities with incompatible units "%s" and "%s".)r   r9   r   r   r:   r   )r   r;   r   r   r!   r!   r"   __sub__   s
    zQuantity.__sub__c             C   s0   t |sdS | j|jsdS | |j|jkS )z	
        F)r   r   r9   r:   r   )r   r;   r!   r!   r"   __eq__  s
    zQuantity.__eq__c             C   s   |  | S )z	
        )r>   )r   r;   r!   r!   r"   __ne__  s    zQuantity.__ne__c             C   s   | j || jk S )zCompares two quantities.

        Raises TypeError if the Quantities are of different dimension (e.g. length vs. mass)

        Returns True if self < other, False otherwise.
        )r   r:   r   )r   r;   r!   r!   r"   __lt__  s    zQuantity.__lt__c             C   s   | j || jkS )N)r   r:   r   )r   r;   r!   r!   r"   __ge__  s    zQuantity.__ge__c             C   s   | j || jkS )N)r   r:   r   )r   r;   r!   r!   r"   __gt__  s    zQuantity.__gt__c             C   s   | j || jkS )N)r   r:   r   )r   r;   r!   r!   r"   __le__  s    zQuantity.__le__c             C   s   | j || jk S )N)r   r:   r   )r   r;   r!   r!   r"   r@   !  s    c             C   s  | j |f}|tjkr&tj| \}}n4d}i }|dk	rhx0| D ]$\}}| }||kr@|dg||< q@W xb| j  D ]T\}}| }||kr||g||< qt|||| d | 9 }|| d  |7  < qtW i }	x4|D ],}|| \}}|dkr||	kst||	|< qW t|	dkrt}nt	|	}|
 rL|t}
|
dkrH||
9 }t}||ftj|< t| j|}|dkrx|| }|
 r|tkstt|rt|j}|S )z
        Combine similar component units and scale, to form an
        equal Quantity in simpler units.

        Returns underlying value type if unit is dimensionless.
        g      ?Nr   r   )r   r   _reduce_cacheZiter_base_or_scaled_unitsZget_dimension_tupleconversion_factor_toAssertionErrorr   r   r
   is_dimensionlessr   r   r(   r*   )r   Z
guide_unitkeyr   Zvalue_factorZcanonical_unitsuexponentdZnew_base_unitsunit_factorresultr!   r!   r"   reduce_unit&  sN    








zQuantity.reduce_unitc             C   sT   t |r&| j| }t| j|| jS t|r>| |j |j S | j| j|ddS dS )zMultiply a quantity by another object

        Returns a new Quantity that is the product of the self * other,
        unless the resulting unit is dimensionless, in which case the
        underlying value type is returned, instead of a Quantity.
        F)post_multiplyN)r   r   r   r   rN   r   _change_units_with_factor)r   r;   r   r!   r!   r"   __mul__a  s    
zQuantity.__mul__c             C   s:   t |rtdn$t|r$tdn| j| j|ddS dS )zMultiply a scalar by a Quantity

        Returns a new Quantity with the same units as self, but with the value
        multiplied by other.
        z>programmer is surprised __rmul__ was called instead of __mul__T)rO   N)r   NotImplementedErrorr   rP   r   )r   r;   r!   r!   r"   __rmul__y  s
    

zQuantity.__rmul__c             C   s@   t |r| t|d S t|r.| |j |j S | t|d S dS )zDivide a Quantity by another object

        Returns a new Quantity, unless the resulting unit type is dimensionless,
        in which case the underlying value type is returned.
        g      N)r   powr   r   r   )r   r;   r!   r!   r"   __truediv__  s
    zQuantity.__truediv__c             C   s6   t |rtdn t|r$tdn|t| d S dS )zDivide a scalar by a quantity.

        Returns a new Quantity.  The resulting units are the inverse of the self argument units.
        zFprogrammer is surprised __rtruediv__ was called instead of __truediv__g      N)r   rR   r   rT   )r   r;   r!   r!   r"   __rtruediv__  s
    

zQuantity.__rtruediv__c             C   s   t t| j|t| j|S )zRaise a Quantity to a power.

        Generally both the value and the unit of the Quantity are affected by this operation.

        Returns a new Quantity equal to self**exponent.
        )r   rT   r   r   )r   rJ   r!   r!   r"   __pow__  s    zQuantity.__pow__c             C   sH   t | j}| j }| j|| }|dkr<|t |9 }t||dS )z
        Returns square root of a Quantity.

        Raises ArithmeticError if component exponents are not even.
        This behavior can be changed if you present a reasonable real life case to me.
        g      ?)r   r   )mathsqrtr   r   rE   r   )r   Z	new_valuenew_unitrL   r!   r!   r"   rY     s    
zQuantity.sqrtc             O   s   y| j j||}W nj tk
r|   |s*|r2tdt| j dkrFd}n2| j d }x&tdt| j D ]}|| j | 7 }qbW Y nX t|| jS )a  
        Computes the sum of a sequence, with the result having the same unit as
        the current sequence.

        If the value is not iterable, it raises a TypeError (same behavior as if
        you tried to iterate over, for instance, an integer).

        This function can take as arguments any arguments recognized by
        `numpy.sum`. If arguments are passed to a non-numpy array, a TypeError
        is raised
        z&Unsupported arguments for Quantity.sumr   r   )r   sumAttributeErrorr   r   r   r   r   )r   argskwargsZmysumr   r!   r!   r"   r[     s    
zQuantity.sumc             O   sX   y| j j||}W n8 tk
rJ   |s*|r2td|  t| j  j }Y nX t|| jS )a`  
        Computes the mean of a sequence, with the result having the same unit as
        the current sequence.

        If the value is not iterable, it raises a TypeError

        This function can take as arguments any arguments recognized by
        `numpy.mean`. If arguments are passed to a non-numpy array, a TypeError
        is raised
        z'Unsupported arguments for Quantity.mean)r   meanr\   r   r[   r   r   r   )r   r]   r^   r_   r!   r!   r"   r_     s    zQuantity.meanc             O   s   y| j j||}W nn tk
r   |s*|r2td|  j }d}x"| j D ]}|| }||| 7 }qHW |t| j  }t|}Y nX t|| j	S )av  
        Computes the square root of the variance of a sequence, with the result
        having the same unit as the current sequence.

        If the value is not iterable, it raises a TypeError

        This function can take as arguments any arguments recognized by
        `numpy.std`. If arguments are passed to a non-numpy array, a TypeError
        is raised
        z&Unsupported arguments for Quantity.stdr   )
r   stdr\   r   r_   r   rX   rY   r   r   )r   r]   r^   r`   r_   varvalZresr!   r!   r"   r`     s    
zQuantity.stdc             O   sN   y| j j||}W n. tk
r@   |s*|r2tdt| j }Y nX t|| jS )aj  
        Computes the maximum value of the sequence, with the result having the
        same unit as the current sequence.

        If the value is not iterable, it raises a TypeError

        This function can take as arguments any arguments recognized by
        `numpy.max`. If arguments are passed to a non-numpy array, a TypeError
        is raised
        z&Unsupported arguments for Quantity.max)r   maxr\   r   r   r   )r   r]   r^   Zmymaxr!   r!   r"   rc     s    zQuantity.maxc             O   sN   y| j j||}W n. tk
r@   |s*|r2tdt| j }Y nX t|| jS )aj  
        Computes the minimum value of the sequence, with the result having the
        same unit as the current sequence.

        If the value is not iterable, it raises a TypeError

        This function can take as arguments any arguments recognized by
        `numpy.min`. If arguments are passed to a non-numpy array, a TypeError
        is raised
        z&Unsupported arguments for Quantity.min)r   minr\   r   r   r   )r   r]   r^   Zmyminr!   r!   r"   rd   %  s    zQuantity.minCc             C   s:   yt | jj||d| jS  tk
r4   tdY nX dS )z
        Same as numpy.ndarray.reshape, except the result is a Quantity with the
        same units as the current object rather than a plain numpy.ndarray
        )orderz1Only numpy array Quantity objects can be reshapedN)r   r   reshaper   r\   )r   shaperf   r!   r!   r"   rg   9  s    zQuantity.reshapec             C   s   t t| j| jS )z
        Return absolute value of a Quantity.

        The unit is unchanged.  A negative value of self will result in a positive value
        in the result.
        )r   absr   r   )r   r!   r!   r"   __abs__D  s    zQuantity.__abs__c             C   s   t | j
 | jS )z.
        Returns a reference to self.
        )r   r   r   )r   r!   r!   r"   __pos__M  s    zQuantity.__pos__c             C   s   t | j | jS )z_Negate a Quantity.

        Returns a new Quantity with a different sign on the value.
        )r   r   r   )r   r!   r!   r"   __neg__S  s    zQuantity.__neg__c             C   s
   t | jS )zLReturns True if value underlying Quantity is zero, False otherwise.
        )r   r   )r   r!   r!   r"   __nonzero__Z  s    zQuantity.__nonzero__c             C   s
   t | jS )N)r   r   )r   r!   r!   r"   __bool___  s    zQuantity.__bool__c             C   s   t t| j| jS )N)r   complexr   r   )r   r!   r!   r"   __complex__b  s    zQuantity.__complex__c             C   s   t t| j| jS )N)r   floatr   r   )r   r!   r!   r"   	__float__d  s    zQuantity.__float__c             C   s   t t| j| jS )N)r   intr   r   )r   r!   r!   r"   __int__f  s    zQuantity.__int__c             C   s   t t| j| jS )N)r   rs   r   r   )r   r!   r!   r"   __long__h  s    zQuantity.__long__c             C   s    |  |}t|r|jS |S dS )zC
        Returns underlying value, in the specified units.
        N)in_units_ofr   r   )r   r   rb   r!   r!   r"   r:   k  s    
zQuantity.value_in_unitc             C   s    |  |}t|r|jS |S dS )zb
        Returns the underlying value type, after conversion to a particular unit system.
        N)in_unit_systemr   r   )r   systemrM   r!   r!   r"   value_in_unit_systemu  s    
zQuantity.value_in_unit_systemc             C   s$   | | j}| j|}| ||S )zb
        Returns a new Quantity equal to this one, expressed in a particular unit system.
        )Zexpress_unitr   rE   rP   )r   rx   Z	new_unitsfr!   r!   r"   rw     s    zQuantity.in_unit_systemc             C   s6   | j |std| j |f | j |}| ||S )a  
        Returns an equal Quantity expressed in different units.

        If the units are the same as those in self, a reference to self is returned.
        Raises a TypeError if the new unit is not compatible with the original unit.

        The post_multiply argument is used in case the multiplication operation is not commutative.
          i.e. result = factor * value when post_multiply is False
          and  result = value * factor when post_multiply is True
        z+Unit "%s" is not compatible with Unit "%s".)r   r9   r   rE   rP   )r   Z
other_unitrz   r!   r!   r"   rv     s    zQuantity.in_units_ofTc          	   C   s   d}y|dkrd}W n t k
r(   Y nX |rBtt| j|}n^y(|rT| j| }n
|| j }t||}W n4 tk
r   t| j}t| ||||}Y nX | r|jS |S d S )NFg      ?T)r   r   r(   r*   r   r   _scale_sequencerG   )r   rZ   factorrO   Zfactor_is_identityrM   r   r!   r!   r"   rP     s&    
z"Quantity._change_units_with_factorc                s@  yr|  }n | }W n t k
r:   yr~t|trVt fdd|D }qxntt|D ]}||   ||< qdW nHt|trt fdd|D }n&x$tt|D ]} ||  ||< qW W nl t k
r4   t|trt fdd|D }n.x,tt|D ]}||  ||< qW Y nX Y nX |S )Nc                s   g | ]}|  qS r!   r!   ).0x)r|   r!   r"   
<listcomp>  s    z,Quantity._scale_sequence.<locals>.<listcomp>c                s   g | ]} | qS r!   r!   )r}   r~   )r|   r!   r"   r     s    c                s   g | ]} | qS r!   )r{   )r}   r~   )r|   rO   r   r!   r"   r     s    )r   r   tupler   r   r{   )r   r   r|   rO   r   r!   )r|   rO   r   r"   r{     s*    


(zQuantity._scale_sequencec             C   s
   t | jS )z5
        Return size of internal value type.
        )r   r   )r   r!   r!   r"   __len__  s    zQuantity.__len__c             C   s$   t | j| rtt| j| | jS )z<
        Keep the same units on contained elements.
        )r   r   rF   r   r   )r   rH   r!   r!   r"   __getitem__  s    zQuantity.__getitem__c             C   s   t |tr8|t| }xvt| D ]}|| | |< q"W nX| j rLt|rLn"| j|jsntd| j|jf || j | j	|< t
| j	| rtd S )Nz+Unit "%s" is not compatible with Unit "%s".)r   sliceindicesr   r   r   rG   r9   r   r   r   rF   )r   rH   r   r   r   r!   r!   r"   __setitem__  s    
zQuantity.__setitem__c             C   s   | j |= d S )N)r   )r   rH   r!   r!   r"   __delitem__  s    zQuantity.__delitem__c             C   s   | j || jS )N)r   __contains__r:   r   )r   r    r!   r!   r"   r     s    zQuantity.__contains__c             c   s"   x| j D ]}t|| jV  qW d S )N)r   r   r   )r   r    r!   r!   r"   __iter__  s    zQuantity.__iter__c             C   s   | j || jS )N)r   countr:   r   )r   r    r!   r!   r"   r     s    zQuantity.countc             C   s   | j || jS )N)r   indexr:   r   )r   r    r!   r!   r"   r     s    zQuantity.indexc             C   s>   t |r| j|| jS t| jr2| j|S tdd S )Nz5Cannot append item without units into list with units)r   r   r   r:   r   rG   r   )r   r    r!   r!   r"   r     s
    
zQuantity.appendc             C   s   | j || j d S )N)r   extendr:   r   )r   Zrhsr!   r!   r"   r     s    zQuantity.extendc             C   s   | j ||| j d S )N)r   insertr:   r   )r   r   r    r!   r!   r"   r     s    zQuantity.insertc             C   s   | j | d S )N)r   remove)r   r    r!   r!   r"   r   
  s    zQuantity.removec             G   s   | j j| | j S )N)r   popr   )r   r]   r!   r!   r"   r     s    zQuantity.pop)NN)N)re   )T)Br4   
__module____qualname____doc__Z__array_priority__r#   r&   r'   r)   r,   r.   r2   r6   r8   r<   r=   r>   r?   r@   rA   rB   rC   rD   rN   rQ   rS   rU   Z__div__rV   Z__rdiv__rW   rY   r[   r_   r`   rc   rd   rg   rj   rk   rl   rm   rn   rp   rr   rt   ru   r:   ry   rw   rv   rP   r{   r   r   r   r   r   r   r   r   r   r   r   r   r   r!   r!   r!   r"   r   S   s   
L		
;	
	


#r   c             C   s
   t | tS )z;
    Returns True if x is a Quantity, False otherwise.
    )r   r   )r~   r!   r!   r"   r     s    r   c             C   s*   t | r|  S t| r"| j S dS dS )z
    TN)r   rG   r   r   )r~   r!   r!   r"   rG     s
    
rG   __main__)r   Z
__future__r   r   r   
__author____version__Zparmed.utils.sixr   r   Zparmed.utils.six.movesr   rX   r(   Zstandard_dimensionsr   r
   r   r   objectr   rm   r   rG   r4   ZdoctestsysZtestmodmodulesr!   r!   r!   r"   <module>E   s,        D