B
    d$3                @   sT  d Z ddlmZ yddlmZmZ W n$ ek
rH   edZedZY nX ddlZddl	Z	ddl
Z
ddlZddlZeeee
j ddd Zeeeej ddd 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  ddl!Z!ddl"m#Z# ddl"m$Z$ dZ%de$j& Ze$j'Z(G dd de)Z*G dd de*Z+G dd de*Z,G dd de+Z-G dd de+Z.G dd de+Z/G d d! d!e+Z0G d"d# d#e+Z1G d$d% d%e+e,Z2G d&d' d'e*Z3G d(d) d)e3Z4G d*d+ d+eZ5d,d- Z6G d.d/ d/e)Z7d?d1d2Z8G d3d4 d4e7Z9G d5d6 d6e9Z:d7d8 Z;d9d: Z<d@d=d>Z=dS )Aa  
This module provides facilities for studying impulsive events.  A number of
multi-dimensional binning functions are provided, as well as code to
convolve binned data with integral- and phase-preserving window functions
to produce smoothed representations of data sets.  This is particularly
well suited for use in computing moving-average rate data from collections
of impulsive events, elliminating binning artifacts from histograms, and
smoothing contour plots.
    )reduce)PosInfNegInfz+infz-infN.   )signaltools)segments)ligolw)array)param)types   )	iterutils)git_versionz"Kipp Cannon <kipp.cannon@ligo.org>z	git id %sc               @   s   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eddfddZed!ddZed"ddZedd Zdd Zedd  ZdS )#Binsz
	Parent class for 1-dimensional binnings.  This class is not
	intended to be used directly, but to be subclassed for use in real
	bins classes.
	c             C   s   t dS )zJ
		Initialize a Bins instance.  Subclasses must override this
		method.
		N)NotImplementedError)self r   U/work/yifan.wang/ringdown/master-ringdown-env/lib/python3.7/site-packages/lal/rate.py__init__`   s    zBins.__init__c             C   s   t dS )zQ
		The number of bins in the binning.  Subclasses must
		override this method.
		N)r   )r   r   r   r   __len__g   s    zBins.__len__c             C   s   t dS )z
		Two binnings are the same if they are instances of the same
		class, and describe the same binnings.  Subclasses should
		override this method but need not.
		N)r   )r   otherr   r   r   __eq__n   s    zBins.__eq__c             C   s^   |j dk	r$|j dkr$tdt| t|jdk	r:| |j nd|jdk	rT| |j d nt| S )a  
		Convert a co-ordinate to a bin index.  The co-ordinate can
		be a single value, or a Python slice instance describing a
		range of values.  If a single value is given, it is mapped
		to the bin index corresponding to that value.  If a slice
		is given, it is converted to a slice whose lower bound is
		the index of the bin in which the slice's lower bound
		falls, and whose upper bound is 1 greater than the index of
		the bin in which the slice's upper bound falls.  Steps are
		not supported in slices.

		Subclasses must override this method, but may chain to this
		to handle slices:

		def __getitem__(self, x):
			if type(x) is slice:
				return super(type(self), self).__getitem__(x)
			# now handle non-slices ...
		Nr   zstep not supported: %sr   )stepr   reprslicestartstoplen)r   xr   r   r   __getitem__v   s    zBins.__getitem__c             C   s   t dS )z
		If __iter__ does not exist, Python uses __getitem__ with
		range(0) as input to define iteration. This is nonsensical
		for bin objects, so explicitly unsupport iteration.
		Subclasses do not need to override this method.
		N)r   )r   r   r   r   __iter__   s    zBins.__iter__c             C   s   t dS )z
		Return an array containing the locations of the lower
		boundaries of the bins.  Subclasses should override this
		method.
		N)r   )r   r   r   r   lower   s    z
Bins.lowerc             C   s   t dS )zm
		Return an array containing the locations of the bin
		centres.  Subclasses should override this method.
		N)r   )r   r   r   r   centres   s    zBins.centresc             C   s   t dS )z
		Return an array containing the locations of the upper
		boundaries of the bins.  Subclasses should override this
		method.
		N)r   )r   r   r   r   upper   s    z
Bins.upperg      ?Nc             c   s  t | dk rtd|jdk	r.tdt| tj}tj}| 	 }| 
 }| | t |\}}}	|jdk	r|| |jks~t|j||< |jdk	r||d  |jkst|j||d < ||| ||  r|d7 }|||d  ||d   r|d8 }||k stdtt|| }
t|}t|}tt||
|| rJtdx>tj|||dD ]*\}}||| || ||
|  fV  q\W dS )a  
		Generator yielding a sequence of x, ln(P(x)) tuples where x
		is a randomly-chosen co-ordinate and P(x) is the PDF from
		which x has been drawn evaluated at x.  Each co-ordinate is
		drawn uniformly from within a bin, which has been drawn
		from a distribution whose CDF goes as [bin index]^{n}.  For
		more information on how bins are drawn, see
		lal.iterutils.randindex.

		If a domain is given, the values returned fall within
		[start, stop].  If start or stop is None, the corresponding
		end of the binning is used.  If start or stop does not
		correspond exactly to a bin boundary, the probability of
		drawing a value from that bin is unchanged, but the values
		drawn from that bin will be restricted to the allowed part
		of the bin (the PDF is adjusted to reflect this).  No
		values are returned from bins with infinite size (after
		clipping them to the requested domain), and the PDF is
		adjusted to reflect this.

		Example:

		>>> import math
		>>> # natural log of 1/10
		>>> print("%.15g" % math.log(1./10))
		-2.30258509299405
		>>> # linear bins spanning [0, 10]
		>>> bins = LinearBins(0, 10, 5)
		>>> # draw a random value, ln P(value) = ln 1/10
		>>> next(bins.randcoord())      # doctest: +ELLIPSIS
		(..., -2.3025850929940455)
		>>> # binning with infinite boundaries
		>>> bins = ATanBins(-1, +1, 4)
		>>> # will ask for values in [0.5, +inf], i.e. the last two
		>>> # bins, but values from final bin will be disallowed, so
		>>> # return values will be uniform in part of the second
		>>> # last bin, [0.5, 0.6366]
		>>> print("%.15g" % math.log(1. / (bins.upper()[-2] - 0.5)))
		1.99055359585182
		>>> next(bins.randcoord(domain = slice(0.5, None)))  # doctest: +ELLIPSIS
		(..., 1.9905535958518226)
		>>> # things that aren't supported:
		>>> # domain slice with a step
		>>> next(LinearBins(0, 10, 1).randcoord(domain = slice(None, None, 2)))
		Traceback (most recent call last):
			...
		NotImplementedError: step not supported: slice(None, None, 2)

		Subclasses should not override this method.
		r   zempty binningNzstep not supported: %szslice too smallz!unavoidable infinite bin detected)n)r   
ValueErrorr   r   r   mathisinfrandomuniformr"   r$   indicesr   AssertionErrorr   tuplenumpyloganymapr   Z	randindex)r   r%   domainr(   r*   lulohi_Zln_dxiZln_Pir   r   r   	randcoord   s8    3




zBins.randcoordpylal_rate_binsc             C   s   d| |f S )zV
		For internal use by XML I/O code.  Subclasses should not
		override this method.
		z%s:%sr   )namesuffixr   r   r   xml_bins_name_enc  s    zBins.xml_bins_name_encc             C   s(   |  dd} | d |kr t| | d S )zV
		For internal use by XML I/O code.  Subclasses should not
		override this method.
		:r   r   )rsplitr&   )r;   r<   r   r   r   xml_bins_name_dec  s    zBins.xml_bins_name_decc             C   s(   |j tjj ko&|do&|| |jkS )zV
		For internal use by XML I/O code.  Subclasses should not
		override this method.
		Name)tagNamer	   ParamhasAttributerA   rB   )clselemr;   r   r   r   xml_bins_check"  s    zBins.xml_bins_checkc             C   s   t dS )z
		Construct a LIGO Light Weight XML representation of the
		Bins instance.  Subclasses must override this method to be
		serializable to LIGO Light Weight XML, otherwise they need
		not override it.
		N)r   )r   r   r   r   to_xml*  s    zBins.to_xmlc             C   s   t dS )z
		From the XML Param element at xml, return the Bins object
		it describes.  Subclasses must override this method to be
		de-serializable from LIGO Light Weight XML, otherwise they
		need not override it.
		N)r   )rF   xmlr   r   r   from_xml3  s    zBins.from_xml)r:   )r:   )__name__
__module____qualname____doc__r   r   r   r    r!   r"   r#   r$   r   r9   staticmethodr=   rA   classmethodrH   rI   rK   r   r   r   r   r   Z   s"   	[
	r   c               @   s<   e Zd ZdZdd Zdd Zdd Zdd	 Zed
d Z	dS )LoHiCountBinsz
	Base class to help implement binnings that can be defined by a
	lower bound, an upper bound, and a count of bins.  This is not a
	binning.
	c             C   sL   t |tst||dk r"t|||kr6t||f|| _|| _|| _dS )z
		The three arguments are the minimum and maximum of the
		values spanned by the bins, and the number of bins to place
		between them.
		r   N)
isinstanceint	TypeErrorr&   minmaxr%   )r   rV   rW   r%   r   r   r   r   D  s    
zLoHiCountBins.__init__c             C   s   | j S )N)r%   )r   r   r   r   r   T  s    zLoHiCountBins.__len__c             C   s.   t |t| o,| j| j| jf|j|j|jfkS )z
		Two binnings are the same if they are instances of the same
		class, have the same lower and upper bounds, and the same
		count of bins.
		)rS   typerV   rW   r%   )r   r   r   r   r   r   W  s    zLoHiCountBins.__eq__c             C   sD   t j| | jdtjd | jtjd | jtjd | j	f S )z
		Construct a LIGO Light Weight XML representation of the
		Bins instance.  Subclasses must define the .xml_bins_name
		class attribute.
		z%s,%s,%sreal_8int_8s)
ligolw_paramrD   from_pyvaluer=   xml_bins_nameligolw_types
FormatFuncrV   rW   r%   )r   r   r   r   rI   c  s    zLoHiCountBins.to_xmlc             C   sf   |  || jstdt|  |jd\}}}tjd |}tjd |}tjd |}| |||S )z
		From the XML Param element at xml, return the Bins object
		it describes.  Subclasses must define the .xml_bins_name
		class attribute.
		znot a %s,rY   rZ   )rH   r]   r&   r   pcdatasplitr^   ToPyType)rF   rJ   r5   r6   r%   r   r   r   rK   k  s    zLoHiCountBins.from_xmlN)
rL   rM   rN   rO   r   r   r   rI   rQ   rK   r   r   r   r   rR   >  s   rR   c                   sh   e Zd ZdZdd Zdd Zdd Z fdd	Zd
d Zdd Z	dd Z
dZdd Zedd Z  ZS )IrregularBinsa  
	Bins with arbitrary, irregular spacing.  We only require strict
	monotonicity of the bin boundaries.  N boundaries define N-1 bins.

	Example:

	>>> x = IrregularBins([0.0, 11.0, 15.0, numpy.inf])
	>>> len(x)
	3
	>>> x[1]
	0
	>>> x[1.5]
	0
	>>> x[13]
	1
	>>> x[25]
	2
	>>> x[4:17]
	slice(0, 3, None)
	>>> IrregularBins([0.0, 15.0, 11.0])
	Traceback (most recent call last):
		...
	ValueError: non-monotonic boundaries provided
	>>> y = IrregularBins([0.0, 11.0, 15.0, numpy.inf])
	>>> x == y
	True
	>>> import sys
	>>> x.to_xml().write(sys.stdout)	# doctest: +NORMALIZE_WHITESPACE
	<Param Type="lstring" Name="irregularbins:pylal_rate_bins:param">0,11,15,inf</Param>
	>>> IrregularBins.from_xml(x.to_xml()) == x
	True
	c             C   sn   t |dk rtdt|| _| jdd | jdd k rHtdt| jd t| jd  | _| _dS )z
		Initialize a set of custom bins with the bin boundaries.
		This includes all left edges plus the right edge.  The
		boundaries must be monotonic and there must be at least two
		elements.
		r   z!less than two boundaries providedNr?   r   z!non-monotonic boundaries providedr   )	r   r&   r.   r
   
boundariesr0   floatr5   r6   )r   re   r   r   r   r     s     zIrregularBins.__init__c             C   s   t |t| o| j|jk S )zh
		Two binnings are the same if they are instances of the same
		class, and have the same boundaries.
		)rS   rX   re   all)r   r   r   r   r   r     s    zIrregularBins.__eq__c             C   s   t | jd S )Nr   )r   re   )r   r   r   r   r     s    zIrregularBins.__len__c                sp   t |tkrtt| |S | j|  kr4| jk rLn n| jj|ddd S || jkrdt	| jd S t
|d S )Nright)Zsider   r   )rX   r   superrd   r    r5   r6   re   searchsortedr   
IndexError)r   r   )	__class__r   r   r      s    
zIrregularBins.__getitem__c             C   s   | j d d S )Nr?   )re   )r   r   r   r   r"     s    zIrregularBins.lowerc             C   s   | j dd  S )Nr   )re   )r   r   r   r   r$     s    zIrregularBins.upperc             C   s   |   |   d S )Ng       @)r"   r$   )r   r   r   r   r#     s    zIrregularBins.centresZirregularbinsc             C   s*   t j| | jdttjd | j	S )zN
		Construct a LIGO Light Weight XML representation of the
		Bins instance.
		r`   rY   )
r[   rD   r\   r=   r]   joinr1   r^   r_   re   )r   r   r   r   rI     s    zIrregularBins.to_xmlc             C   s:   |  || jstdt|  | ttjd |jdS )zO
		From the XML Param element at xml, return the Bins object
		it describes.
		znot a %srY   r`   )	rH   r]   r&   r   r1   r^   rc   ra   rb   )rF   rJ   r   r   r   rK     s    zIrregularBins.from_xml)rL   rM   rN   rO   r   r   r   r    r"   r$   r#   r]   rI   rQ   rK   __classcell__r   r   )rl   r   rd   {  s    rd   c                   sH   e Zd ZdZ fddZ fddZdd Zdd	 Zd
d ZdZ	  Z
S )
LinearBinsa6  
	Linearly-spaced bins.  There are n bins of equal size, the first
	bin starts on the lower bound and the last bin ends on the upper
	bound inclusively.

	Example:

	>>> x = LinearBins(1.0, 25.0, 3)
	>>> x.lower()
	array([  1.,   9.,  17.])
	>>> x.upper()
	array([  9.,  17.,  25.])
	>>> x.centres()
	array([  5.,  13.,  21.])
	>>> x[1]
	0
	>>> x[1.5]
	0
	>>> x[10]
	1
	>>> x[25]
	2
	>>> x[0:27]
	Traceback (most recent call last):
		...
	IndexError: 0
	>>> x[1:25]
	slice(0, 3, None)
	>>> x[:25]
	slice(0, 3, None)
	>>> x[10:16.9]
	slice(1, 2, None)
	>>> x[10:17]
	slice(1, 3, None)
	>>> x[10:]
	slice(1, 3, None)
	>>> import sys
	>>> x.to_xml().write(sys.stdout)	# doctest: +NORMALIZE_WHITESPACE
	<Param Type="lstring" Name="linbins:pylal_rate_bins:param">1,25,3</Param>
	>>> LinearBins.from_xml(x.to_xml()) == x
	True
	c                s*   t t| ||| t|| | | _d S )N)ri   ro   r   rf   delta)r   rV   rW   r%   )rl   r   r   r     s    zLinearBins.__init__c                st   t |tkrtt| |S | j|  kr4| jk rRn ntt	|| j | j
 S || jkrht| d S t|d S )Nr   )rX   r   ri   ro   r    rV   rW   rT   r'   floorrp   r   rk   )r   r   )rl   r   r   r      s    
zLinearBins.__getitem__c             C   s   t | j| j| j t| S )N)r.   linspacerV   rW   rp   r   )r   r   r   r   r"     s    zLinearBins.lowerc             C   s*   t | j| jd  | j| jd  t| S )Ng       @)r.   rr   rV   rp   rW   r   )r   r   r   r   r#      s    zLinearBins.centresc             C   s   t | j| j | jt| S )N)r.   rr   rV   rp   rW   r   )r   r   r   r   r$   #  s    zLinearBins.upperZlinbins)rL   rM   rN   rO   r   r    r"   r#   r$   r]   rn   r   r   )rl   r   ro     s   *ro   c                   sH   e Zd ZdZ fddZ fddZdd Zdd	 Zd
d ZdZ	  Z
S )LinearPlusOverflowBinsa  
	Linearly-spaced bins with overflow at the edges.  There are n-2
	bins of equal size.  The bin 1 starts on the lower bound and bin
	n-2 ends on the upper bound.  Bins 0 and n-1 are overflow going
	from -infinity to the lower bound and from the upper bound to
	+infinity respectively.  Must have n >= 3.

	Example:

	>>> x = LinearPlusOverflowBins(1.0, 25.0, 5)
	>>> x.centres()
	array([-inf,   5.,  13.,  21.,  inf])
	>>> x.lower()
	array([-inf,   1.,   9.,  17.,  25.])
	>>> x.upper()
	array([  1.,   9.,  17.,  25.,  inf])
	>>> x[float("-inf")]
	0
	>>> x[0]
	0
	>>> x[1]
	1
	>>> x[10]
	2
	>>> x[24.99999999]
	3
	>>> x[25]
	4
	>>> x[100]
	4
	>>> x[float("+inf")]
	4
	>>> x[float("-inf"):9]
	slice(0, 3, None)
	>>> x[9:float("+inf")]
	slice(2, 5, None)
	>>> import sys
	>>> x.to_xml().write(sys.stdout)	# doctest: +NORMALIZE_WHITESPACE
	<Param Type="lstring" Name="linplusoverflowbins:pylal_rate_bins:param">1,25,5</Param>
	>>> LinearPlusOverflowBins.from_xml(x.to_xml()) == x
	True
	c                s>   |dk rt dtt| ||| t|| |d  | _d S )N   zn must be >= 3r   )r&   ri   rs   r   rf   rp   )r   rV   rW   r%   )rl   r   r   r   X  s    zLinearPlusOverflowBins.__init__c                s   t |tkrtt| |S | j|  kr4| jk rVn ntt	|| j | j
 d S || jkrlt| d S || jk rzdS t|d S )Nr   r   )rX   r   ri   rs   r    rV   rW   rT   r'   rq   rp   r   rk   )r   r   )rl   r   r   r    ^  s    

z"LinearPlusOverflowBins.__getitem__c          	   C   s2   t t tgt | j| j| j t| d fS )Nr   )	r.   concatenater
   r   rr   rV   rW   rp   r   )r   r   r   r   r"   m  s    zLinearPlusOverflowBins.lowerc          	   C   sJ   t t tgt | j| jd  | j| jd  t| d t t	gfS )Ng       @r   )
r.   ru   r
   r   rr   rV   rp   rW   r   r   )r   r   r   r   r#   p  s    zLinearPlusOverflowBins.centresc             C   s2   t t | j| j | jt| d t tgfS )Nr   )	r.   ru   rr   rV   rp   rW   r   r
   r   )r   r   r   r   r$   s  s    zLinearPlusOverflowBins.upperZlinplusoverflowbins)rL   rM   rN   rO   r   r    r"   r#   r$   r]   rn   r   r   )rl   r   rs   -  s   *rs   c                   sH   e Zd ZdZ fddZ fddZdd Zdd	 Zd
d ZdZ	  Z
S )LogarithmicBinsa  
	Logarithmically-spaced bins.  There are n bins, each of whose upper
	and lower bounds differ by the same factor.  The first bin starts
	on the lower bound, and the last bin ends on the upper bound
	inclusively.

	Example:

	>>> x = LogarithmicBins(1.0, 25.0, 3)
	>>> x[1]
	0
	>>> x[5]
	1
	>>> x[25]
	2
	>>> import sys
	>>> x.to_xml().write(sys.stdout)	# doctest: +NORMALIZE_WHITESPACE
	<Param Type="lstring" Name="logbins:pylal_rate_bins:param">1,25,3</Param>
	>>> LogarithmicBins.from_xml(x.to_xml()) == x
	True
	c                sB   t t| ||| t|| _t|| _| j| j | | _d S )N)ri   rv   r   r'   r/   logminlogmaxrp   )r   rV   rW   r%   )rl   r   r   r     s    zLogarithmicBins.__init__c                sz   t |tkrtt| |S | j|  kr4| jk rXn n tt	t
|| j | j S || jkrnt| d S t|d S )Nr   )rX   r   ri   rv   r    rV   rW   rT   r'   rq   r/   rw   rp   r   rk   )r   r   )rl   r   r   r      s     
zLogarithmicBins.__getitem__c             C   s"   t t | j| j| j t| S )N)r.   exprr   rw   rx   rp   r   )r   r   r   r   r"     s    zLogarithmicBins.lowerc             C   s,   t t | j| j| j t| | jd  S )Ng       @)r.   ry   rr   rw   rx   rp   r   )r   r   r   r   r#     s    zLogarithmicBins.centresc             C   s"   t t | j| j | jt| S )N)r.   ry   rr   rw   rp   rx   r   )r   r   r   r   r$     s    zLogarithmicBins.upperZlogbins)rL   rM   rN   rO   r   r    r"   r#   r$   r]   rn   r   r   )rl   r   rv   }  s   rv   c                   sH   e Zd ZdZ fddZ fddZdd Zdd	 Zd
d ZdZ	  Z
S )LogarithmicPlusOverflowBinsaC  
	Logarithmically-spaced bins plus one bin at each end that goes to
	zero and positive infinity respectively.  There are n-2 bins each
	of whose upper and lower bounds differ by the same factor.  Bin 1
	starts on the lower bound, and bin n-2 ends on the upper bound
	inclusively.  Bins 0 and n-1 are overflow bins extending from 0 to
	the lower bound and from the upper bound to +infinity respectively.
	Must have n >= 3.

	Example:

	>>> x = LogarithmicPlusOverflowBins(1.0, 25.0, 5)
	>>> x[0]
	0
	>>> x[1]
	1
	>>> x[5]
	2
	>>> x[24.999]
	3
	>>> x[25]
	4
	>>> x[100]
	4
	>>> x.lower()
	array([  0.        ,   1.        ,   2.92401774,   8.54987973,  25.        ])
	>>> x.upper()
	array([  1.        ,   2.92401774,   8.54987973,  25.        ,          inf])
	>>> x.centres()
	array([  0.        ,   1.70997595,   5.        ,  14.62008869,          inf])
	>>> import sys
	>>> x.to_xml().write(sys.stdout)	# doctest: +NORMALIZE_WHITESPACE
	<Param Type="lstring" Name="logplusoverflowbins:pylal_rate_bins:param">1,25,5</Param>
	>>> LogarithmicPlusOverflowBins.from_xml(x.to_xml()) == x
	True
	c                sV   |dk rt dtt| ||| t|| _t|| _| j| j |d  | _d S )Nrt   zn must be >= 3r   )	r&   ri   rz   r   r'   r/   rw   rx   rp   )r   rV   rW   r%   )rl   r   r   r     s    z$LogarithmicPlusOverflowBins.__init__c                s   t |tkrtt| |S | j|  kr4| jk r\n n$dtt	t
|| j | j  S || jkrrt| d S || jk rdS t|d S )Nr   r   )rX   r   ri   rz   r    rV   rW   rT   r'   rq   r/   rw   rp   r   rk   )r   r   )rl   r   r   r      s    $

z'LogarithmicPlusOverflowBins.__getitem__c             C   s2   t t dgt t | j| jt| d fS )Ng        r   )r.   ru   r
   ry   rr   rw   rx   r   )r   r   r   r   r"     s    z!LogarithmicPlusOverflowBins.lowerc             C   sL   t t dgt t | j| j| j t| d | jd  t t	gfS )Ng        r   g       @)
r.   ru   r
   ry   rr   rw   rx   rp   r   r   )r   r   r   r   r#     s    z#LogarithmicPlusOverflowBins.centresc          
   C   s2   t t t | j| jt| d t tgfS )Nr   )	r.   ru   ry   rr   rw   rx   r   r
   r   )r   r   r   r   r$     s    z!LogarithmicPlusOverflowBins.upperZlogplusoverflowbins)rL   rM   rN   rO   r   r    r"   r#   r$   r]   rn   r   r   )rl   r   rz     s   $rz   c                   sH   e Zd ZdZ fddZ fddZdd Zdd	 Zd
d ZdZ	  Z
S )ATanBinsa  
	Bins spaced uniformly in tan^-1 x.  Provides approximately linear
	binning in the middle portion, with the bin density dropping
	asymptotically to 0 as x goes to +/- \infty.  The min and max
	parameters set the bounds of the region of approximately
	uniformly-spaced bins.  In a sense, these are where the roll-over
	from uniformly-spaced bins to asymptotically diminishing bin
	density occurs.  There is a total of n bins.

	Example:

	>>> x = ATanBins(-1.0, +1.0, 11)
	>>> x[float("-inf")]
	0
	>>> x[0]
	5
	>>> x[float("+inf")]
	10
	>>> x.centres()
	array([-4.42778777, -1.39400285, -0.73469838, -0.40913068, -0.18692843,
	        0.        ,  0.18692843,  0.40913068,  0.73469838,  1.39400285,
                4.42778777])
	>>> import sys
	>>> x.to_xml().write(sys.stdout)	# doctest: +NORMALIZE_WHITESPACE
	<Param Type="lstring" Name="atanbins:pylal_rate_bins:param">-1,1,11</Param>
	>>> ATanBins.from_xml(x.to_xml()) == x
	True
	c                sD   t t| ||| || d | _tjt||  | _d| | _d S )Ng       @g      ?)	ri   r{   r   midr'   pirf   scalerp   )r   rV   rW   r%   )rl   r   r   r     s    zATanBins.__init__c                sh   t |tkrtt| |S tt|| j | j	 tj
 d }|dk r\tt|| j S t| d S )Ng      ?g      ?r   )rX   r   ri   r{   r    r'   atanrf   r|   r~   r}   rT   rq   rp   r   )r   r   )rl   r   r   r    $  s    $zATanBins.__getitem__c             C   sD   t t jtj d tj
 d t| dd| j | j }t|d< |S )Ng       @F)endpointr   )	r.   tanrr   r'   r}   r   r~   r|   r   )r   r   r   r   r   r"   0  s    8zATanBins.lowerc             C   sP   dt j | j }ttjt j d | t j
 d | t| dd| j | j S )Ng      ?g       @F)r   )	r'   r}   rp   r.   r   rr   r   r~   r|   )r   offsetr   r   r   r#   5  s    zATanBins.centresc             C   sX   t j| j }ttjt j d | t j
 d | t| dd| j | j }t	|d< |S )Ng       @F)r   r?   )
r'   r}   rp   r.   r   rr   r   r~   r|   r   )r   r   r   r   r   r   r$   9  s    @zATanBins.upperZatanbins)rL   rM   rN   rO   r   r    r"   r#   r$   r]   rn   r   r   )rl   r   r{     s   
r{   c               @   s*   e Zd ZdZdd ZejZdd ZdZdS )ATanLogarithmicBinsaz  
	Provides the same binning as the ATanBins class but in the
	logarithm of the variable.  The min and max parameters set the
	bounds of the interval of approximately logarithmically-spaced
	bins.  In a sense, these are where the roll-over from
	logarithmically-spaced bins to asymptotically diminishing bin
	density occurs.

	Example:

	>>> x = ATanLogarithmicBins(+1.0, +1000.0, 11)
	>>> x[0]
	0
	>>> x[30]
	5
	>>> x[float("+inf")]
	10
	>>> x.centres()
	array([  7.21636246e-06,   2.56445876e-01,   2.50007148e+00,
		 7.69668960e+00,   1.65808715e+01,   3.16227766e+01,
		 6.03104608e+01,   1.29925988e+02,   3.99988563e+02,
		 3.89945831e+03,   1.38573971e+08])
	>>> import sys
	>>> x.to_xml().write(sys.stdout)	# doctest: +NORMALIZE_WHITESPACE
	<Param Type="lstring" Name="atanlogbins:pylal_rate_bins:param">1,1000,11</Param>
	>>> ATanLogarithmicBins.from_xml(x.to_xml()) == x
	True

	It is relatively easy to choose limits and a count of bins that
	result in numerical overflows and underflows when computing bin
	boundaries.  When this happens, one or more bins at the ends of the
	binning ends up with identical upper and lower boundaries (either
	0, or +inf), and this class behaves as though those bins simply
	don't exist.  That is, the actual number of bins can be less than
	the number requested.  len() returns the actual number of bins ---
	how large an array the binning corresponds to.
	c          	   C   s2  t |tst||dk r"t|||kr6t||ft|t| d | _tjt|t|  | _d| | _	t
tj d tj| j	 t
|  | j | j }t
jdd t
|}W d Q R X t
|tdgf}|d d |dd  k}t| |d d |  |d d | _|| _|| _|| _d S )	Nr   g       @g      ?r   ignore)overg        r?   )rS   rT   rU   r&   r'   r/   r|   r}   r~   rp   r.   r   Zarangeerrstatery   hstackr   rd   r   keepersrV   rW   r%   )r   rV   rW   r%   re   r   r   r   r   r   l  s&    

4zATanLogarithmicBins.__init__c          	   C   sv   dt j | j }ttjt j d | t j
 d | | jdd| j | j }tj	dd t
|| j S Q R X d S )Ng      ?g       @F)r   r   )r   )r'   r}   rp   r.   r   rr   r%   r~   r|   r   ry   r   )r   r   r#   r   r   r   r#     s    >zATanLogarithmicBins.centresZatanlogbinsN)	rL   rM   rN   rO   r   rd   r   r#   r]   r   r   r   r   r   F  s
   %
r   c               @   sP   e Zd ZdZdd Zdd Zdd Zdd	 Zd
d ZdZ	dd Z
edd ZdS )
Categoriesaa  
	Categories is a many-to-one mapping from a value to an integer
	category index.  A value belongs to a category if it is contained
	in the category's defining collection.  If a value is contained in
	more than one category's defining collection, it belongs to the
	category with the smallest index.  IndexError is raised if a value
	is not contained in any category's defining collection.

	Example with discrete values:

	>>> categories = Categories([
	...	set((frozenset(("H1", "L1")), frozenset(("H1", "V1")))),
	...	set((frozenset(("H1", "L1", "V1")),))
	... ])
	>>> categories[set(("H1", "L1"))]
	0
	>>> categories[set(("H1", "V1"))]
	0
	>>> categories[set(("H1", "L1", "V1"))]
	1

	Example with continuous values:

	>>> from ligo.segments import *
	>>> categories = Categories([
	...	segmentlist([segment(1, 3), segment(5, 7)]),
	...	segmentlist([segment(0, PosInfinity)])
	... ])
	>>> categories[2]
	0
	>>> categories[4]
	1
	>>> categories[-1]
	Traceback (most recent call last):
		...
	IndexError: -1

	This last example demonstrates the behaviour when the intersection
	of the categories is not the empty set.
	c             C   s   t || _dS )z
		categories is an iterable of containers (objects that
		support the "in" operator) defining the categories.
		Objects will be mapped to the integer index of the
		container that contains them.
		N)r-   
containers)r   
categoriesr   r   r   r     s    zCategories.__init__c             C   s
   t | jS )N)r   r   )r   r   r   r   r     s    zCategories.__len__c             C   s0   x"t | jD ]\}}||kr|S qW t|dS )z
		Return i if value is contained in i-th container.  If value
		is not contained in any of the containers, raise an
		IndexError.  This is O(n).
		N)	enumerater   rk   )r   valuer8   sr   r   r   r      s    zCategories.__getitem__c             C   s   t |t| o| j|jkS )N)rS   rX   r   )r   r   r   r   r   r     s    zCategories.__eq__c             C   s   | j S )N)r   )r   r   r   r   r#     s    zCategories.centresZcategorybinsc             C   s   t j| | jd| jS )zN
		Construct a LIGO Light Weight XML representation of the
		Bins instance.
		yaml)r[   rD   buildr=   r]   r   )r   r   r   r   rI     s    zCategories.to_xmlc             C   s(   |  || jstdt|  | |jS )zO
		From the XML Param element at xml, return the Bins object
		it describes.
		znot a %s)rH   r]   r&   r   ra   )rF   rJ   r   r   r   rK     s    zCategories.from_xmlN)rL   rM   rN   rO   r   r   r    r   r#   r]   rI   rQ   rK   r   r   r   r   r     s   (
r   c                   s,   e Zd ZdZ fddZdd ZdZ  ZS )HashableBinsa  
	Maps hashable objects (things that can be used as dictionary keys)
	to integers.

	Example:
	>>> x = HashableBins([
	...    frozenset(("H1", "L1")),
	...    frozenset(("H1", "V1")),
	...    frozenset(("L1", "V1")),
	...    frozenset(("H1", "L1", "V1"))
	... ])
	>>> x[frozenset(("H1", "L1"))]
	0
	>>> x[set(("H1", "L1"))]	# equal, but not hashable
	Traceback (most recent call last):
		...
	IndexError: set(['H1', 'L1'])
	>>> x.centres()[2]
	frozenset(['V1', 'L1'])
	c                s0   t t| | tt| jtt| j| _d S )N)	ri   r   r   dictzipr   ranger   mapping)r   Z	hashables)rl   r   r   r     s    zHashableBins.__init__c          	   C   s0   y
| j | S  ttfk
r*   t|Y nX d S )N)r   KeyErrorrU   rk   )r   r   r   r   r   r      s    
zHashableBins.__getitem__Zhashablebins)rL   rM   rN   rO   r   r    r]   rn   r   r   )rl   r   r     s   r   c            
   @   s   e Zd ZdZdd Zdd Zdd Zedd	 Zd
d Z	dd Z
dd Zdd ZdddZedd eeeeeeeefD Zeeee e  dd Zedd ZdS )NDBinsa"  
	Multi-dimensional co-ordinate binning.  An instance of this object
	is used to convert a tuple of co-ordinates into a tuple of bin
	indices.  This can be used to allow the contents of an array object
	to be accessed with real-valued coordinates.

	NDBins is a subclass of the tuple builtin, and is initialized with
	an iterable of instances of subclasses of Bins.  Each Bins subclass
	instance describes the binning to apply in the corresponding
	co-ordinate direction, and the number of them sets the dimensions
	of the binning.

	Example:

	>>> x = NDBins((LinearBins(1, 25, 3), LogarithmicBins(1, 25, 3)))
	>>> x[1, 1]
	(0, 0)
	>>> x[1.5, 1]
	(0, 0)
	>>> x[10, 1]
	(1, 0)
	>>> x[1, 5]
	(0, 1)
	>>> x[1, 1:5]
	(0, slice(0, 2, None))
	>>> x.centres()
	(array([  5.,  13.,  21.]), array([  1.70997595,   5.        ,  14.62008869]))
	>>> y = NDBins((LinearBins(1, 25, 3), LogarithmicBins(1, 25, 3)))
	>>> x == y
	True
	>>> y = NDBins((LinearBins(1, 25, 4), LogarithmicBins(1, 25, 3)))
	>>> x == y
	False
	>>> y = NDBins((LogarithmicBins(1, 25, 3), LogarithmicBins(1, 25, 3)))
	>>> x == y
	False
	>>> from ligo.lw.ligolw import LIGO_LW
	>>> import sys
	>>> elem = x.to_xml(LIGO_LW())
	>>> elem.write(sys.stdout)	# doctest: +NORMALIZE_WHITESPACE
	<LIGO_LW>
		<Param Type="lstring" Name="linbins:pylal_rate_bins:param">1,25,3</Param>
		<Param Type="lstring" Name="logbins:pylal_rate_bins:param">1,25,3</Param>
	</LIGO_LW>
	>>> NDBins.from_xml(elem) == x
	True

	Note that the co-ordinates to be converted must be a tuple, even if
	it is only a 1-dimensional co-ordinate.
	c             C   sz   t dd |D | _dddd tt|D ddd tt|D f }i }t|t | |d }|| | _d S )Nc             s   s   | ]}|j V  qd S )N)r    ).0binningr   r   r   	<genexpr>E  s    z"NDBins.__init__.<locals>.<genexpr>zBdef __realcall__(self, %s):
	_getitems = self._getitems
	return %sz, c             s   s   | ]}d | V  qdS )zx%dNr   )r   r8   r   r   r   r   N  s    c             s   s   | ]}d ||f V  qdS )z_getitems[%d](x%d)Nr   )r   r8   r   r   r   r   N  s    __realcall__)	r-   Z	_getitemsrm   r   r   execglobals__get__r   )r   ZbinningsZdefine__realcall__r3   r   r   r   r   r   D  s    	<zNDBins.__init__c             C   s   t |tr| | S t| |S )au  
		When coords is a tuple this is a synonym for self(*coords),
		otherwise coords is interpreted as an index into ourselves
		and the Bins object at that index is returned

		Example:

		>>> x = NDBins((LinearBins(1, 25, 3), LogarithmicBins(1, 25, 3)))
		>>> x[1, 1]
		(0, 0)
		>>> # slices can be given syntactically
		>>> x[10:12, 1]
		(slice(1, 2, None), 0)
		>>> type(x[1])
		<class 'lal.rate.LogarithmicBins'>

		Note that if the argument is to be interpreted as a
		co-ordinate it must be a tuple even if it is only a
		1-dimensional co-ordinate.

		Example:

		>>> x = NDBins((LinearBins(1, 25, 3),))
		>>> x[1,]
		(0,)
		)rS   r-   r    )r   coordsr   r   r   r    T  s    zNDBins.__getitem__c             G   s
   | j | S )a  
		Convert an N-dimensional co-ordinate to an N-tuple of bin
		indices using the Bins instances in this object.  Calling
		the NDBins instance instead of using indexing (the "[]"
		operator) provides a more direct, faster, interface to the
		Bins instances contained herein, but slices cannot be given
		syntactically in the argument list

		Example:

		>>> x = NDBins((LinearBins(1, 25, 3), LogarithmicBins(1, 25, 3)))
		>>> x[1, 1]	# access using index operator
		(0, 0)
		>>> x(1, 1)	# access by calling
		(0, 0)
		>>> x = NDBins((LinearBins(1, 25, 3),))
		>>> x(1)	# explicit tuples not required for 1D
		(0,)
		>>> x = NDBins((LinearBins(1, 25, 1000),))
		>>> x(slice(10, 12))	# slices (constructed manually)
		(slice(375, 459, None),)
		>>> x = NDBins((Categories([set(("Cow", "Chicken", "Goat")), set(("Tractor", "Plough")), set(("Barn", "House"))]),))
		>>> x("Cow")
		(0,)

		Each co-ordinate can be anything the corresponding Bins
		instance will accept.
		)r   )r   r   r   r   r   __call__q  s    zNDBins.__call__c             C   s   t dd | D S )z
		Tuple of the number of bins along each dimension.

		Example:

		>>> NDBins((LinearBins(0, 6, 3), LinearBins(0, 10, 5))).shape
		(3, 5)
		c             s   s   | ]}t |V  qd S )N)r   )r   br   r   r   r     s    zNDBins.shape.<locals>.<genexpr>)r-   )r   r   r   r   shape  s    
zNDBins.shapec             C   s   t dd | D S )a  
		Return a tuple of arrays, where each array contains the
		locations of the lower boundaries of the bins in the
		corresponding dimension.

		Example:

		>>> NDBins((LinearBins(0, 6, 3), LinearBins(0, 10, 5))).lower()
		(array([ 0.,  2.,  4.]), array([ 0.,  2.,  4.,  6.,  8.]))
		c             s   s   | ]}|  V  qd S )N)r"   )r   r   r   r   r   r     s    zNDBins.lower.<locals>.<genexpr>)r-   )r   r   r   r   r"     s    zNDBins.lowerc             C   s   t dd | D S )a  
		Return a tuple of arrays, where each array contains the
		locations of the bin centres for the corresponding
		dimension.

		Example:

		>>> NDBins((LinearBins(0, 6, 3), LinearBins(0, 10, 5))).centres()
		(array([ 1.,  3.,  5.]), array([ 1.,  3.,  5.,  7.,  9.]))
		c             s   s   | ]}|  V  qd S )N)r#   )r   r   r   r   r   r     s    z!NDBins.centres.<locals>.<genexpr>)r-   )r   r   r   r   r#     s    zNDBins.centresc             C   s   t dd | D S )a   
		Return a tuple of arrays, where each array contains the
		locations of the upper boundaries of the bins in the
		corresponding dimension.

		Example:

		>>> NDBins((LinearBins(0, 6, 3), LinearBins(0, 10, 5))).upper()
		(array([ 2.,  4.,  6.]), array([  2.,   4.,   6.,   8.,  10.]))
		c             s   s   | ]}|  V  qd S )N)r$   )r   r   r   r   r   r     s    zNDBins.upper.<locals>.<genexpr>)r-   )r   r   r   r   r$     s    zNDBins.upperc             C   s   t dd t|  |  D }t|dkr4|d S y"tjdddt| f| S  tk
r   t	tj
|}t dd |D |_|S X dS )	a3  
		Return an n-dimensional array of the bin volumes.

		Example:

		>>> # 3x5 grid of bins, each 2 units by 2 units
		>>> x = NDBins((LinearBins(0, 6, 3), LinearBins(0, 10, 5)))
		>>> x.volumes()
		array([[ 4.,  4.,  4.,  4.,  4.],
		       [ 4.,  4.,  4.,  4.,  4.],
		       [ 4.,  4.,  4.,  4.,  4.]])
		c             s   s   | ]\}}|| V  qd S )Nr   )r   r4   r3   r   r   r   r     s    z!NDBins.volumes.<locals>.<genexpr>r   r   r`   abcdefghijklmnopqrstuvwxyzNc             s   s   | ]}t |V  qd S )N)r   )r   vr   r   r   r     s    )r-   r   r$   r"   r   r.   einsumrm   AttributeErrorr   outerr   )r   volumesresultr   r   r   r     s     "zNDBins.volumesNc             c   s   |dkrdt |  }|dkr0tddft |  }tdd t| ||D }x:tdd |D d}|ddd t|d	dd fV  qLW dS )
a  
		Generator yielding a sequence of (x0, x1, ...), ln(P(x0,
		x1, ...)) tuples where (x0, x1, ...) is a randomly-chosen
		co-ordinate in the N-dimensional binning and P(x0, x1, ...)
		is the PDF from which the co-ordinate tuple has been drawn
		evaluated at those co-ordinates.  If ns is not None it must
		be a sequence of floats whose length matches the dimension
		of the binning.  The floats will set the exponents, in
		order, of the CDFs for the generators used for each
		co-ordinate.  If domain is not None it must be a sequence
		of slice objects whose length matches the dimension of the
		binning.  The slice objects will be passed, in order, as
		the domain keyword argument to the .randcoord() method
		corresponding to each dimension.  For more information on
		how each of the co-ordinates is drawn, see
		Bins.randcoord().

		Example:

		>>> binning = NDBins((LinearBins(0, 10, 5), LinearBins(0, 10, 5)))
		>>> coord = binning.randcoord()
		>>> next(coord)	# doctest: +ELLIPSIS
		((..., ...), -4.6051701859880909)
		N)g      ?c             s   s&   | ]\}}}t |j||d V  qdS ))r2   N)iterr9   )r   r   r%   dr   r   r   r     s    z#NDBins.randcoord.<locals>.<genexpr>c             s   s   | ]}t |V  qd S )N)next)r   Zcoordgenr   r   r   r     s    r   r   r   r   )r   r   r-   r   sum)r   nsr2   Z	coordgensseqr   r   r   r9     s    zNDBins.randcoordc             c   s   | ]}|j |fV  qd S )N)r]   )r   rF   r   r   r   r     s    zNDBins.<genexpr>c             C   s    x| D ]}| |  qW |S )a  
		Construct a LIGO Light Weight XML representation of the
		NDBins instance.  The representation is an in-order list of
		Param elements, typically inserted inside a LIGO_LW
		element.  The elem argument provides the XML element to
		which the Param elements should be appended as children.

		NOTE:  The decoding process will require a specific parent
		element to be provided, and all Param elements that are
		immediate children of that element and contain the correct
		suffix will be used to reconstruct the NDBins.  At this
		time the suffix is undocumented, so to guarantee
		compatibility the Param elements should not be inserted
		into an element that might contain other, unrelated, Params
		among its immediate children.
		)appendChildrI   )r   rG   r   r   r   r   rI     s    
zNDBins.to_xmlc          	      s   g }xP|j D ]F}|jtjjkr qyt|j W n tk
rF   wY nX || qW |sjtdt	|   fdd|D S )z
		From the XML document tree rooted at xml construct an
		return an NDBins object described by the Param elements
		therein.  Note, the XML element must be the immediate
		parent of the Param elements describing the NDBins.
		zno Param elements found at '%s'c                s$   g | ]} j t|j |qS r   )xml_bins_name_mappingr   rA   rB   rK   )r   rG   )rF   r   r   
<listcomp>-  s    z#NDBins.from_xml.<locals>.<listcomp>)
Z
childNodesrC   r	   rD   r   rA   rB   r&   appendr   )rF   rJ   paramsrG   r   )rF   r   rK     s    zNDBins.from_xml)NN) rL   rM   rN   rO   r   r    r   propertyr   r"   r#   r$   r   r9   r   ro   rs   rv   rz   r{   r   r   r   r   updatelistr   valueskeysrI   rQ   rK   r   r   r   r   r     s   2
&"r   c                sZ   |   }|  } tt|d |d g@  tj fddt||D |jt	| dS )a$  
	Input is a Bins subclass instance and a ligo.segments.segmentlist
	instance.  The output is an array object the length of the binning,
	which each element in the array set to the interval in the
	corresponding bin spanned by the segment list.

	Example:

	>>> from ligo.segments import *
	>>> s = segmentlist([segment(1.5, 10.333), segment(15.8, 24)])
	>>> b = LinearBins(0, 30, 100)
	>>> bins_spanned(b, s)
	array([ 0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.3  ,  0.3  ,  0.3  ,
	        0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,
	        0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,
	        0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,
	        0.3  ,  0.3  ,  0.133,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,
	        0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,
	        0.   ,  0.   ,  0.   ,  0.   ,  0.1  ,  0.3  ,  0.3  ,  0.3  ,
	        0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,
	        0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,
	        0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,  0.3  ,
	        0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,
	        0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,
	        0.   ,  0.   ,  0.   ,  0.   ])
	r   r?   c             3   s"   | ]}t  t|g@ V  qd S )N)absr   segmentlist)r   seg)seglistr   r   r   Y  s    zbins_spanned.<locals>.<genexpr>)dtypecount)
r"   r$   r   r   segmentr.   Zfromiterr   r   r   )binsr   r"   r$   r   )r   r   bins_spanned9  s     r   c               @   s   e 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edd Zedd ZdS )!BinnedArraya  
	A convenience wrapper, using the NDBins class to provide access to
	the elements of an array object.  Technical reasons preclude
	providing a subclass of the array object, so the array data is made
	available as the "array" attribute of this class.

	Examples:

	Note that even for 1 dimensional arrays the index must be a tuple.

	>>> x = BinnedArray(NDBins((LinearBins(0, 10, 5),)))
	>>> x.at_centres()
	array([ 0.,  0.,  0.,  0.,  0.])
	>>> x[0,] += 1
	>>> x[0.5,] += 1
	>>> x.at_centres()
	array([ 2.,  0.,  0.,  0.,  0.])
	>>> x.argmax()
	(1.0,)

	Note the relationship between the binning limits, the bin centres,
	and the co-ordinates of the BinnedArray

	>>> x = BinnedArray(NDBins((LinearBins(-0.5, 1.5, 2), LinearBins(-0.5, 1.5, 2))))
	>>> x.bins.centres()
	(array([ 0.,  1.]), array([ 0.,  1.]))
	>>> x[0, 0] = 0
	>>> x[0, 1] = 1
	>>> x[1, 0] = 2
	>>> x[1, 1] = 4
	>>> x.at_centres()
	array([[ 0.,  1.],
	       [ 2.,  4.]])
	>>> x[0, 0]
	0.0
	>>> x[0, 1]
	1.0
	>>> x[1, 0]
	2.0
	>>> x[1, 1]
	4.0
	>>> x.argmin()
	(0.0, 0.0)
	>>> x.argmax()
	(1.0, 1.0)

	A BinnedArray can be initialized from an existing array if the
	array's shape is the same as the binning's.

	>>> import numpy
	>>> x = BinnedArray(NDBins((LinearBins(0, 10, 5),)), array = numpy.zeros((5,)))
	>>> x = BinnedArray(NDBins((LinearBins(0, 10, 5),)), array = numpy.zeros((5,1)))
	Traceback (most recent call last):
		...
	ValueError: bins (shape = (5,)) and array (shape = (5, 1)), if supplied, must have the same shape

	A BinnedArray can be serialized to LIGO Light Weight XML.

	>>> import sys
	>>> x = BinnedArray(NDBins((LinearBins(-0.5, 1.5, 2), LinearBins(-0.5, 1.5, 2))))
	>>> elem = x.to_xml("test")
	>>> elem.write(sys.stdout)	# doctest: +NORMALIZE_WHITESPACE
	<LIGO_LW Name="test:pylal_rate_binnedarray">
		<Param Type="lstring" Name="linbins:pylal_rate_bins:param">-0.5,1.5,2</Param>
		<Param Type="lstring" Name="linbins:pylal_rate_bins:param">-0.5,1.5,2</Param>
		<Array Type="real_8" Name="array:array">
			<Dim>2</Dim>
			<Dim>2</Dim>
			<Stream Delimiter=" " Type="Local">
				0 0
				0 0
			</Stream>
		</Array>
	</LIGO_LW>
	>>> y = BinnedArray.from_xml(elem, "test")
	>>> y.bins == x.bins
	True
	>>> (y.array == x.array).all()
	True
	Ndoublec             C   sV   || _ |d kr"tj|j|d| _n0|j|jkrLtdt|jt|jf n|| _d S )N)r   zObins (shape = %s) and array (shape = %s), if supplied, must have the same shape)r   r.   Zzerosr   r
   r&   str)r   r   r
   r   r   r   r   r     s    zBinnedArray.__init__c             C   s   | j | j|  S )N)r
   r   )r   r   r   r   r   r      s    zBinnedArray.__getitem__c             C   s   || j | j| < d S )N)r
   r   )r   r   valr   r   r   __setitem__  s    zBinnedArray.__setitem__c             C   s
   t | jS )N)r   r
   )r   r   r   r   r     s    zBinnedArray.__len__c             C   s0   | j |j krtdt| |  j|j7  _| S )a  
		Add the contents of another BinnedArray object to this one.
		Both must have identical binnings.

		Example:

		>>> x = BinnedArray(NDBins((LinearBins(-0.5, 1.5, 2), LinearBins(-0.5, 1.5, 2))))
		>>> x[0, 0] = 0
		>>> x[0, 1] = 1
		>>> x[1, 0] = 2
		>>> x[1, 1] = 4
		>>> x.at_centres()
		array([[ 0.,  1.],
		       [ 2.,  4.]])
		>>> x += x
		>>> x.at_centres()
		array([[ 0.,  2.],
		       [ 4.,  8.]])
		zincompatible binning: %s)r   rU   r   r
   )r   r   r   r   r   __iadd__  s    zBinnedArray.__iadd__c             C   s   |   } | |7 } | S )aZ  
		Add two BinnedArray objects together.

		Example:

		>>> x = BinnedArray(NDBins((LinearBins(-0.5, 1.5, 2), LinearBins(-0.5, 1.5, 2))))
		>>> x[0, 0] = 0
		>>> x[0, 1] = 1
		>>> x[1, 0] = 2
		>>> x[1, 1] = 4
		>>> x.at_centres()
		array([[ 0.,  1.],
		       [ 2.,  4.]])
		>>> (x + x).at_centres()
		array([[ 0.,  2.],
		       [ 4.,  8.]])
		)copy)r   r   r   r   r   __add__  s    zBinnedArray.__add__c             C   s   t | | j| j S )z[
		Return a copy of the BinnedArray.  The .bins attribute is
		shared with the original.
		)rX   r   r
   r   )r   r   r   r   r     s    zBinnedArray.copyc             C   s
   | j  S )zO
		Return a tuple of arrays containing the bin centres for
		each dimension.
		)r   r#   )r   r   r   r   r#     s    zBinnedArray.centresc             C   s   | j S )a  
		Return an array of the BinnedArray's value evaluated at the
		bin centres.  In many cases this is simply a reference to
		the internal array object, but for subclasses that override
		item retrieval and assignment some additional work might be
		required to obtain this array.  In those cases, this method
		is a convenience wrapper to avoid coding the evaluation
		logic in the calling code.

		Because subclasses expect to be able to override this, in
		almost all cases calling code that wishes to access the
		values stored in the internal array directly should
		probably use this method to do so.

		NOTE:

		- The return value might be a newly-constructed object or a
		  reference to an internal object.
		)r
   )r   r   r   r   
at_centres  s    zBinnedArray.at_centresc             C   s2   |   }tdd t|  t| |jD S )z
		Return the co-ordinates of the bin centre containing the
		minimum value.  Same as numpy.argmin(), converting the
		indexes to bin co-ordinates.
		c             s   s   | ]\}}|| V  qd S )Nr   )r   r#   indexr   r   r   r   "  s    z%BinnedArray.argmin.<locals>.<genexpr>)r   r-   r   r#   r.   unravel_indexargminr   )r   r
   r   r   r   r     s    zBinnedArray.argminc             C   s2   |   }tdd t|  t| |jD S )z
		Return the co-ordinates of the bin centre containing the
		maximum value.  Same as numpy.argmax(), converting the
		indexes to bin co-ordinates.
		c             s   s   | ]\}}|| V  qd S )Nr   )r   r#   r   r   r   r   r   +  s    z%BinnedArray.argmax.<locals>.<genexpr>)r   r-   r   r#   r.   r   argmaxr   )r   r
   r   r   r   r   $  s    zBinnedArray.argmaxc             C   s8   t  }d| |_| j| |tjd| j	 |S )zI
		Retrun an XML document tree describing a rate.BinnedArray
		object.
		z%s:pylal_rate_binnedarrayr
   )
r	   LIGO_LWrB   r   rI   r   ligolw_arrayZArrayr   r
   )r   r;   rG   r   r   r   rI   -  s
    
zBinnedArray.to_xmlc                s\   d    fdd| tjjD }y
|\}W n( tk
rV   tdt| f Y nX |S )Nz%s:pylal_rate_binnedarrayc                s$   g | ]}| d r|j kr|qS )rB   )rE   rB   )r   rG   )r;   r   r   r   ;  s    z,BinnedArray.get_xml_root.<locals>.<listcomp>z>XML tree at '%s' must contain exactly one '%s' LIGO_LW element)ZgetElementsByTagNamer	   r   rC   r&   r   )rF   rJ   r;   rG   r   )r;   r   get_xml_root8  s    
zBinnedArray.get_xml_rootc             C   sV   |  ||}| t|t|djd}|jj|jjkrRtd||jj|jjf |S )a  
		Search for the description of a rate.BinnedArray object
		named "name" in the XML document tree rooted at xml, and
		construct and return a new rate.BinnedArray object from the
		data contained therein.

		NOTE:  the .array attribute is a reference to the .array
		attribute of the XML element.  Changes to the contents of
		the BinnedArray object affect the XML document tree.
		r
   )r
   z8'%s' binning shape does not match array shape:  %s != %s)	r   r   rK   r   Z	get_arrayr
   r   r   r&   )rF   rJ   r;   rG   r   r   r   r   rK   B  s
    zBinnedArray.from_xml)Nr   )rL   rM   rN   rO   r   r    r   r   r   r   r   r#   r   r   r   rI   rQ   r   rK   r   r   r   r   r   e  s   P
			
r           c                s  t dd t j  j  j D }  tjdgj	 dfgj	 dg }x|D ]}t
| \}t|dkst| |  	x(	k r|d  | krd7 qW x(	k r|	d  |	 kr	d8 	qW 	k st|t	d  qdW t dd t||D }t | t|dkryt W n tk
r   |d |d d |d d	  	tjd
d6 dd dd	  dd dd	   W dQ R X tj	fddS X t|d ddddfddS t|dkrby
t W n tk
r4   |d |d d d	  
d d	  dd dd	  dd dd	  tjd
dV ddddf dd	ddf  ddddf dddd	f  W dQ R X tj
fddS X t|d |d jddddfddS yt W n$ tk
r    fddS X tttj| jdfddS dS )a_  
	Wrapper constructing a scipy.interpolate interpolator from the
	contents of a BinnedArray.  Only piecewise linear interpolators are
	supported.  In 1 and 2 dimensions, scipy.interpolate.interp1d and
	.interp2d is used, respectively.  In more than 2 dimensions
	scipy.interpolate.LinearNDInterpolator is used.

	Example:

	One dimension

	>>> x = BinnedArray(NDBins((LinearBins(-0.5, 2.5, 3),)))
	>>> x[0,] = 0
	>>> x[1,] = 1
	>>> x[2,] = 3
	>>> y = InterpBinnedArray(x)
	>>> y(0)
	0.0
	>>> y(1)
	1.0
	>>> y(2)
	3.0
	>>> y(0.5)
	0.5
	>>> y(1.5)
	2.0

	Two dimensions

	>>> x = BinnedArray(NDBins((LinearBins(-0.5, 2.5, 3), LinearBins(-0.5, 1.5, 2))))
	>>> x[0, 0] = 0
	>>> x[0, 1] = 1
	>>> x[1, 0] = 2
	>>> x[1, 1] = 4
	>>> x[2, 0] = 2
	>>> x[2, 1] = 4
	>>> y = InterpBinnedArray(x)
	>>> y(0, 0)
	0.0
	>>> y(0, 1)
	1.0
	>>> y(1, 0)
	2.0
	>>> y(1, 1)
	4.0
	>>> y(2, 0)
	2.0
	>>> y(2, 1)
	4.0
	>>> y(0, 0.25)
	0.25
	>>> y(0, 0.75)
	0.75
	>>> y(0.25, 0)
	0.5
	>>> y(0.75, 0)
	1.5
	>>> y(0.25, 1)
	1.75
	>>> y(0.75, 1)
	3.25
	>>> y(1, 0.25)
	2.5
	>>> y(1, 0.75)
	3.5

	BUGS:  Due to bugs in some versions of scipy and numpy, if an old
	version of scipy and/or numpy is detected this code falls back to
	home-grown piece-wise linear interpolator code for 1- and 2
	dimensions that is slow, and in 3- and higher dimensions the
	fall-back is to nearest-neighbour "interpolation".
	c             s   s,   | ]$\}}}t |d  ||d fV  qdS )r   r?   N)r.   r   )r   r3   cr4   r   r   r   r     s    z$InterpBinnedArray.<locals>.<genexpr>)r   r   Zconstant)modeZconstant_valuesr   r   c             s   s   | ]\}}|| V  qd S )Nr   )r   r   r   r   r   r   r     s    r?   r   )invalidNc                sX   |   k rk sn S   | d }| r<| S | |  |  |   S )Nr   )rj   )r   r8   )coords0dz_over_dcoords0
fill_valuer6   r(   r5   zr   r   interp  s    z!InterpBinnedArray.<locals>.interpZlinearF)kindr   Zbounds_errorr   c                 s   t  |  S )N)rf   )r   )r   r   r   <lambda>      z#InterpBinnedArray.<locals>.<lambda>r   c                s>  
|   k rk r.n n|  k r,k s2n S   | d } |d }|  |  |  }||  |  }|| dkrʈ	||f r||f S ||f |||f   |||f   S 	|d |d f r|d |d f S |d |d f d| ||d f    d| |d |f    S )Nr   g      ?)rj   )r   yr8   jZdxZdy)r   coords1dcoords0dcoords1dz0dz1r   hixhiyr(   loxloyr   r   r   r     s    .,c                 s   t  |  S )N)rf   )r   )r   r   r   r     r   c                 s"   y |  S  t k
r   S X d S )N)rk   )r   )binnedarrayr   r   r   r     s    )r   c                 s   t  |  S )N)rf   )r   )r   r   r   r     r   )r-   r   r   r"   r#   r$   r   r.   padndimisfiniteZnonzeror   r,   rV   rW   r   r   Zinterp1d	NameErrorr   r'   r(   Zinterp2dTZLinearNDInterpolatorr   	itertoolsproductZflat)r   r   r   Zslicesr   Zfinite_indexesr   )r   r   r   r   r   r   r   r   r   r6   r   r   r   r(   r5   r   r   r   r   InterpBinnedArray`  sl    Q,&
:
(2$ r   c                   s@   e Zd ZdZ fddZdd Zdd Zdd	 Zd
d Z  Z	S )BinnedDensitya  
	Variant of the BinnedArray type that interprets its contents as a
	density.  When initialized, the volumes of the NDBins used to
	create the BinnedDensity are computed and stored in the .volume
	attribute.  When a value is retrieved from a bin, the value
	reported is the value stored in the bin divided by the bin's
	volume.  When a value is assigned to a bin, the value recorded is
	the assigned value multiplied by the bin's volume.  The true values
	recorded in the bins can be accessed via the .count attribute,
	which is a BinnedArray object wrapping the same array and binning.

	Because the internal array stores counts, not the densities, when
	the data in an instance of this class is processed with the
	filter_array() function the result is the density of smoothed
	counts, not the smoothed density.  This is best illustrated with an
	example.

	Example:  linear bins

	>>> # bins are 2 units, each
	>>> x = BinnedDensity(NDBins((LinearBins(0, 10, 5),)))
	>>> x.volume
	array([ 2.,  2.,  2.,  2.,  2.])
	>>> # set count at 5 to 1
	>>> x.count[5.0,] = 1
	>>> # internal array set to 1 in that bin
	>>> x.array
	array([ 0.,  0.,  1.,  0.,  0.])
	>>> # density reported is 0.5
	>>> print x[5.0,]
	0.5
	>>> # convolve counts with 3-bin top hat window
	>>> filter_array(x.array, tophat_window(3))
	array([ 0.        ,  0.33333333,  0.33333333,  0.33333333,  0.        ])
	>>> # density at 5 is now 1/6 counts / unit interval
	>>> print x[5.0,]
	0.166666666667
	>>> # total count has been preserved
	>>> print x.array.sum()
	1.0

	Example:  logarithmic bins

	>>> # bins increase in size by a factor of 2, each
	>>> x = BinnedDensity(NDBins((LogarithmicBins(1, 32, 5),)))
	>>> x.volume
	array([  1.,   2.,   4.,   8.,  16.])
	>>> # set count at 5 to 1
	>>> x.count[5.0,] = 1
	>>> # internal array set to 1 in that bin
	>>> x.array
	array([ 0.,  0.,  1.,  0.,  0.])
	>>> # density reported is 0.25
	>>> print x[5.0,]
	0.25
	>>> # convolve counts with 3-bin top hat window
	>>> filter_array(x.array, tophat_window(3))
	array([ 0.        ,  0.33333333,  0.33333333,  0.33333333,  0.        ])
	>>> # density at 5 is now 1/12 counts / unit interval
	>>> print x[5.0,]
	0.0833333333333
	>>> # density is 1/6 in bin below
	>>> print x[3,]
	0.166666666667
	>>> # and 1/24 in bin above
	>>> print x[10,]
	0.0416666666667
	>>> # total count has been preserved
	>>> print x.array.sum()
	1.0

	Explanation.  In the linear case there are five bins spanning the
	interval [0, 10], making each bin 2 "units" in size.  A single
	count is placed at 5.0, which is bin number 2.  The averaging
	filter is a top-hat window 3 bins wide.  The single count in bin
	#2, when averaged over the three bins around it, becomes an average
	of 1/3 count per bin, each of which is 2 units in size, so the
	average density is 1/6 events / unit.  The count has been spread
	out over several bins but the integral of the density, the total
	count, is unchanged.  The logarithmic case is identical but because
	the bin sizes are non-uniform, when the single count is spread
	across the three bins the density is non-uniform.  The integral is
	still preserved.

	Some binnings have infinite-sized bins.  For such bins, the counts
	may be manipulated directly, as usual, but for all finite counts a
	density of 0 will be reported for those bins (and NaN for infinite
	counts), and ValueError will be raised if an attempt is made to
	assign a density to those bins.

	NOTES:

	- While it is technically possible to modify the binning parameters
	  after creating an instance of this class, the steps required to
	  bring all internal data back into consistency following such a
	  change are undocumented.  One should consider the metadata
	  carried by these objects to be immutable.
	c                s4   t t| j|| t| j| jd| _| j | _d S )N)r
   )	ri   r   r   r   r   r
   r   r   volume)r   argskwargs)rl   r   r   r   }  s    zBinnedDensity.__init__c             C   s   | j | }| j| | j|  S )N)r   r
   r   )r   r   r   r   r   r      s    
zBinnedDensity.__getitem__c             C   s<   | j | }| j| }t| r*td|| | j|< d S )NzScannot assign density values to infinite-volume bins, try assigning a count instead)r   r   r.   r(   r0   r&   r
   )r   r   r   Zvolr   r   r   r     s
    

zBinnedDensity.__setitem__c             C   s   | j | j S )N)r
   r   )r   r   r   r   r     s    zBinnedDensity.at_centresc             C   s8   t | t| jd| | j|d d  | jj|dS )a  
		Return a new BinnedDensity object containing the density
		integrated over dimension dim.

		Example:

		>>> # 5x5 mesh of bins, each with volume = 4
		>>> x = BinnedDensity(NDBins((LinearBins(0, 10, 5), LinearBins(0, 10, 5))))
		>>> # set count at 5,5 to 1
		>>> x.count[5.0, 5.0] = 1
		>>> # density in central bin is 1/4
		>>> x.at_centres()
		array([[ 0.  ,  0.  ,  0.  ,  0.  ,  0.  ],
		       [ 0.  ,  0.  ,  0.  ,  0.  ,  0.  ],
		       [ 0.  ,  0.  ,  0.25,  0.  ,  0.  ],
		       [ 0.  ,  0.  ,  0.  ,  0.  ,  0.  ],
		       [ 0.  ,  0.  ,  0.  ,  0.  ,  0.  ]])
		>>> # convolve counts with 3-bin top-hat window
		>>> filter_array(x.array, tophat_window(3, 3))
		array([[ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ],
		       [ 0.        ,  0.11111111,  0.11111111,  0.11111111,  0.        ],
		       [ 0.        ,  0.11111111,  0.11111111,  0.11111111,  0.        ],
		       [ 0.        ,  0.11111111,  0.11111111,  0.11111111,  0.        ],
		       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ]])
		>>> # density is now 1/(4 * 9) = 1/36 in 9 central bins
		>>> x.at_centres()
		array([[ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ],
		       [ 0.        ,  0.02777778,  0.02777778,  0.02777778,  0.        ],
		       [ 0.        ,  0.02777778,  0.02777778,  0.02777778,  0.        ],
		       [ 0.        ,  0.02777778,  0.02777778,  0.02777778,  0.        ],
		       [ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ]])
		>>> # densities still sum (not integrate) to 1/4
		>>> x.at_centres().sum()
		0.25
		>>> # integrate over dimension 1
		>>> x = x.marginalize(1)
		>>> # bin volumes are now 2
		>>> # densities in 3 central bins are = 1/(2 * 3) = 1/6
		>>> x.at_centres()
		array([ 0.        ,  0.16666667,  0.16666667,  0.16666667,  0.        ])
		>>> # densities sum (not integrate) to 1/2
		>>> x.at_centres().sum()
		0.5
		Nr   )Zaxis)rX   r   r   r
   r   )r   dimr   r   r   marginalize  s    -zBinnedDensity.marginalize)
rL   rM   rN   rO   r   r    r   r   r   rn   r   r   )rl   r   r     s   br   c                   s   e Zd ZdZ fddZ fddZdd Zdd	 Z fd
dZ fddZ	 fddZ
 fddZ fddZdd Z fddZe fddZ  ZS )BinnedLnPDFa{  
	Variant of the BinnedDensity class that (i) tracks a normalization
	which it uses to rescale the reported density so that its integral
	is one, and (ii) reports the natural logarithm of that normalized
	density.

	The .normalize() method needs to be invoked after any manipulation
	of the contents of the array before the results of .__getitem__()
	are meaningful.

	How the normalization is tracked should be assumed to be
	undocumented.  In this class the .norm attribute contains the
	natural log of the sum of the counts in all bins and this value is
	subtracted from the natural log of the (unnormalized) density in
	the .__getitem__() method, but subclasses are free to implement
	whatever unique mechanism is appropriate for themselves.

	As with the BinnedDensity class, the internal array contains counts
	(not densities, nor natural logarithms of densities), and the
	.count attribute continues to be a BinnedArray interface to those
	counts.  The intention is for the counts themselves to provide an
	additional degree of freedom apart from the normalized density.
	For example, see the .__iadd__() method where it is assumed that
	the total count encodes a relative weight to be used when
	marginalizing over two PDFs.

	Example:

	>>> # 5x5 mesh of bins each with volume = 4
	>>> x = BinnedLnPDF(NDBins((LinearBins(0, 10, 5), LinearBins(0, 10, 5))))
	>>> # set count at 5,5 to 36 and normalize
	>>> x.count[5.0, 5.0] = 36
	>>> x.normalize()
	>>> # log probability density = ln 1/4 = -1.3862943611198906
	>>> x.at_centres()
	array([[       -inf,        -inf,        -inf,        -inf,        -inf],
	       [       -inf,        -inf,        -inf,        -inf,        -inf],
	       [       -inf,        -inf, -1.38629436,        -inf,        -inf],
	       [       -inf,        -inf,        -inf,        -inf,        -inf],
	       [       -inf,        -inf,        -inf,        -inf,        -inf]])
	>>> # convolve with 3x3 top-hat window.  in general one must renormalize after this, but we'll skip that here because the demo is constructed so as to not need it
	>>> filter_array(x.array, tophat_window(3, 3))
	array([[ 0.,  0.,  0.,  0.,  0.],
	       [ 0.,  4.,  4.,  4.,  0.],
	       [ 0.,  4.,  4.,  4.,  0.],
	       [ 0.,  4.,  4.,  4.,  0.],
	       [ 0.,  0.,  0.,  0.,  0.]])
	>>> # ln probability density = ln 1/(4 * 9) = -3.58351893845611
	>>> x.at_centres()
	array([[       -inf,        -inf,        -inf,        -inf,        -inf],
	       [       -inf, -3.58351894, -3.58351894, -3.58351894,        -inf],
	       [       -inf, -3.58351894, -3.58351894, -3.58351894,        -inf],
	       [       -inf, -3.58351894, -3.58351894, -3.58351894,        -inf],
	       [       -inf,        -inf,        -inf,        -inf,        -inf]])
	>>> # .marginzlize() preserves normalization
	>>> y = x.marginalize(1)
	>>> y.count.at_centres()
	array([  0.,  12.,  12.,  12.,   0.])
	>>> # ln probability density = ln 1/(2 * 3) = -1.791759469228055
	>>> y.at_centres()
	array([       -inf, -1.79175947, -1.79175947, -1.79175947,        -inf])
	>>> # assuming \sqrt{N} counting fluctuations, compute the fractional uncertainty
	>>> import numpy
	>>> d = BinnedArray(x.bins, 1. / numpy.sqrt(x.count.at_centres()))
	>>> d.at_centres()
	array([[ inf,  inf,  inf,  inf,  inf],
	       [ inf,  0.5,  0.5,  0.5,  inf],
	       [ inf,  0.5,  0.5,  0.5,  inf],
	       [ inf,  0.5,  0.5,  0.5,  inf],
	       [ inf,  inf,  inf,  inf,  inf]])
	c                s   t t| j|| |   d S )N)ri   r   r   	normalize)r   r   r   )rl   r   r   r     s    zBinnedLnPDF.__init__c                s   t tt| || j S )N)r.   r/   ri   r   r    norm)r   r   )rl   r   r   r      s    zBinnedLnPDF.__getitem__c             C   s   t dd S )NzQitem assignment operation not defined.  assign to .count then invoke .normalize())r   )r   r   r   r   r   r   r     s    5zBinnedLnPDF.__setitem__c             C   s   t | S )a  
		Return an interpolator to evaluate the density as a smooth
		function of co-ordinates.  If the density has not been
		normalized the interpolator's behaviour is undefined.

		NOTE:  the interpolator is the InterpBinnedArray object
		which might have limitations in 3 or more dimensions.  See
		its documentation for more information.

		NOTE:  in the future this is likely to be replaced with
		some sort of internal mechanism.

		Example:

		>>> # 5-bin linear mesh of bins each with volume = 2
		>>> x = BinnedLnPDF(NDBins((LinearBins(0, 10, 5), )))
		>>> # set some counts and normalize
		>>> x.count[3,] = x.count[7,] = 1
		>>> x.count[5,] = 2
		>>> x.normalize()
		>>> x.count.at_centres()
		array([ 0.,  1.,  2.,  1.,  0.])
		>>> x.at_centres()
		array([       -inf, -2.07944154, -1.38629436, -2.07944154,        -inf])
		>>> # construct interpolator object
		>>> x = x.mkinterp()
		>>> # log(P) is interpolated linearly, so it can be counter-intuitive where the density drops to 0 in neighbouring bins
		>>> x(3)
		-inf
		>>> # otherwise behaviour is as expected
		>>> x(4)
		-1.732867951399863
		>>> x(4.5)
		-1.5595811562598769
		>>> x(5)
		-1.3862943611198906
		)r   )r   r   r   r   mkinterpK  s    &zBinnedLnPDF.mkinterpc          	      s4   t jddd t tt|  | j S Q R X d S )Nr   )divider   )r.   r   r/   ri   r   r   r   )r   )rl   r   r   r   s  s    zBinnedLnPDF.at_centresc                s   t t| |}| j|_|S )N)ri   r   r   r   )r   r   new)rl   r   r   r   w  s    zBinnedLnPDF.marginalizec                sd   t t| | | j|jkr@|  jtt|j| j 7  _n |jtt| j|j  | _| S )aW  
		Adds the counts array and normalization of other to self.
		If the original two PDFs were normalized then the result is
		also normalized, otherwise .normalize() needs to be invoked
		to normalize the result.  Once normalized, the result is
		the PDF marginalized over the two original PDFs (the sum of
		the two PDFs weighted by the relative total frequency of
		events in each).

		Example:

		>>> # 5-bin linear mesh of bins each with volume = 2
		>>> x = BinnedLnPDF(NDBins((LinearBins(0, 10, 5), )))
		>>> y = BinnedLnPDF(NDBins((LinearBins(0, 10, 5), )))
		>>> x.count[5,] = 2
		>>> y.count[3,] = 2
		>>> x.normalize()
		>>> y.normalize()
		>>> x.at_centres()
		array([       -inf,        -inf, -0.69314718,        -inf,        -inf])
		>>> x += y
		>>> x.at_centres()
		array([       -inf, -1.38629436, -1.38629436,        -inf,        -inf])
		)ri   r   r   r   r'   log1pry   )r   r   )rl   r   r   r   |  s
    $ zBinnedLnPDF.__iadd__c                s   t t| |} |   | S )N)ri   r   r   r   )r   r   )rl   r   r   r     s    zBinnedLnPDF.__add__c                s   t t|  }| j|_|S )N)ri   r   r   r   )r   r   )rl   r   r   r     s    zBinnedLnPDF.copyc             C   sF   | j  | _| jdks&t| js&t| jdkr<t| jnt| _dS )a  
		Updates the internal normalization.  Subclasses override
		this with custom normalization mechanisms if required.

		Note that counts contained in infinite-sized bins are
		included in the normalization.  In this way the relative
		frequency with which counts occur in those bins is
		accounted for in the normalization although the density
		reported for those bins will be 0.
		g        N)r
   r   r   r'   isnanr,   r/   r   )r   r   r   r   r     s    zBinnedLnPDF.normalizec                s,   t t| j||}|tjd| j |S )Nr   )ri   r   rI   r   r[   rD   r\   r   )r   r   r   rG   )rl   r   r   rI     s    zBinnedLnPDF.to_xmlc                s0   |  ||}tt| ||}t|d|_|S )Nr   )r   ri   r   rK   r[   Zget_pyvaluer   )rF   rJ   r;   rG   r   )rl   r   r   rK     s    zBinnedLnPDF.from_xml)rL   rM   rN   rO   r   r    r   r   r   r   r   r   r   r   rI   rQ   rK   rn   r   r   )rl   r   r     s   G7(-r   c              O   s,  | st dt| dk r(t dt|  |dd}|rJt dd| g }xZ| D ]R}tt|| d d	 }t	|d
 |r|t
| nt}||jj|j  qTW t|d
kr|d S y2t|dksttjdddt| f| S  tk
r&   ttj|}tdd |D |_|S X dS )a  
	Generate a normalized (integral = 1) Gaussian window in N
	dimensions.  The bins parameters set the width of the window in bin
	counts in each dimension.  The optional keyword argument sigma,
	which defaults to 10, sets the size of the array in all dimensions
	in units of the width in each dimension.  The sizes are adjusted so
	that the array has an odd number of samples in each dimension, and
	the Gaussian is peaked on the middle sample.

	Example:

	>>> # 2D window with width of 1.5 bins in first dimension,
	>>> # 1 bin in second dimension, 3 widths long (rounded to odd
	>>> # integer = 5 x 3 bins) in each dimension
	>>> gaussian_window(1.5, 1, sigma = 3)
	array([[ 0.00161887,  0.01196189,  0.00161887],
	       [ 0.02329859,  0.17215456,  0.02329859],
	       [ 0.05667207,  0.41875314,  0.05667207],
	       [ 0.02329859,  0.17215456,  0.02329859],
	       [ 0.00161887,  0.01196189,  0.00161887]])
	z"function requires at least 1 widthg        z#widths must be non-negative, got %ssigma
   z$unrecognized keyword argument(s): %sr`   g       @r   r   r      r   Nc             s   s   | ]}t |V  qd S )N)r   )r   wr   r   r   r   	  s    z"gaussian_window.<locals>.<genexpr>)r&   rV   r   poprm   rT   r'   rq   lalZCreateGaussREAL8Windowrf   r   r   datar   r   r,   r.   r   r   r   r   r-   r   )r   r   r  windowsr   r3   r  windowr   r   r   gaussian_window  s*    
 "r
  c              G   s   | st dt| dkr(t dt|  g }x@| D ]8}ttt|d d d }||j	j	|j
  q2W t|dkr|d S y2t|dksttjdd	d
t| f| S  tk
r   ttj|}tdd |D |_|S X d
S )a7  
	Generate a normalized (integral = 1) rectangular window in N
	dimensions.  The bins parameters set the width of the window in bin
	counts in each dimension, each of which must be positive and will
	be rounded up to the nearest odd integer.

	Example:

	>>> tophat_window(4)
	array([ 0.2,  0.2,  0.2,  0.2,  0.2])
	>>> tophat_window(4, 4)
	array([[ 0.04,  0.04,  0.04,  0.04,  0.04],
	       [ 0.04,  0.04,  0.04,  0.04,  0.04],
	       [ 0.04,  0.04,  0.04,  0.04,  0.04],
	       [ 0.04,  0.04,  0.04,  0.04,  0.04],
	       [ 0.04,  0.04,  0.04,  0.04,  0.04]])
	z"function requires at least 1 widthr   zwidths must be positive, got %sg       @r   r   r  r`   r   Nc             s   s   | ]}t |V  qd S )N)r   )r   r  r   r   r   r   2	  s    z tophat_window.<locals>.<genexpr>)r&   rV   r   r  ZCreateRectangularREAL8WindowrT   r'   rq   r   r  r   r   r,   r.   r   rm   r   r   r   r-   r   )r   r  r   r  r	  r   r   r   tophat_window	  s"    
 "r  FTc             C   s  |rt t| j}|t|jkr(tddtdj|jkrBtdg }x|t|D ]p}|j| | j| kr| j| d d d d }|j| | d }|t|||  qP|td|j|  qPW |t	| }|rjt
| }	x|  rft
| }
t|
}|||dk  d k}~d| |< d|
| < ~tj|
|dd	}
t|
}d|
|| d
 k < ~|	|
7 }	~
qW ntj| |dd	}	t
j| |	dd | S )a  
	Filter an array using the window function.  The transformation is
	done in place.  The data are assumed to be 0 outside of their
	domain of definition.  The window function must have an odd number
	of samples in each dimension;  this is done so that it is always
	clear which sample is at the window's centre, which helps prevent
	phase errors.  If the window function's size exceeds that of the
	data in one or more dimensions, the largest allowed central portion
	of the window function in the affected dimensions will be used.
	This is done silently;  to determine if window function truncation
	will occur, check for yourself that your window function is smaller
	than your data in all dimensions.

	If use_fft is True, the window is convolved with the array using
	FFT convolution.  This is done by processing the array in bands of
	relatively small dynamic range each to work around numerical
	dynamic range limitation in the FFTs, but the window function is
	not treated similarly.  As a result the window is effectively
	limited to a few orders of magnitude of dynamic range.  If use_fft
	is False there are no dynamic range issues but the convolution can
	be quite slow.
	z$array and window dimensions mismatchr   r   z9window size is not an odd integer in at least 1 dimensionr   g     @g        Zsame)r   g+=no)Zcasting)r,   r   r   r&   r1   __and__r   r   r   r-   r.   Z
zeros_liker0   r   r   rV   r   ZfftconvolverW   ZconvolveZcopyto)ar	  ZcyclicZuse_fftZdimsZwindow_slicesr   r%   firstr   Z	workspaceZabs_workspacemaskr   r   r   filter_array?	  sB    



r  )r   )FT)>rO   	functoolsr   Zfpconstr   r   ImportErrorrf   r   r'   r.   r)   Zscipyr-   r1   rT   __version__striprb   Z__numpy__version__Z__scipy__version__Zscipy.signalr   Zligor   Zligo.lwr	   r
   r   r   r[   r   r^   r   r   r   
__author__iddate__date__objectr   rR   rd   ro   rs   rv   rz   r{   r   r   r   r   r   r   r   r   r   r
  r  r  r   r   r   r   <module>#   sj   ""
 e=gKP8LEJ_"  *, |
 ; ,  34