
    ph!                        S r SSKJr  SSKrSSKJr  SSKJr  SSK	J
r
  SSKJrJrJrJrJr  SS	KJrJr  SS
KJr  Sr\R.                  " S\5      r " S S\5      r\" 5       r " S S\R6                  \5      rS r\R<                  R>                  S 5       r \RC                  S5      S 5       r"S r#\RC                  S5      S 5       r$\RC                  SSS9S 5       r%\RC                  S5      S 5       r&\RC                  S5      \" 5       S 5       5       r'\RC                  S SS9\" 5       S! 5       5       r(\RC                  S"SS9\RC                  S#SS9\" 5       S$ 5       5       5       r)S% r*S& r+S' r,g)(z$
Functions for measuring distances.
    )ContextDecoratorN)emd)cdist)entropy   )	Directionconfig	constantsutilsvalidate)flattenmarginal_zero)Registry
   hamming_matricesc                   @   ^  \ rS rSrSrSrU 4S jrSS jrS rSr	U =r
$ )	MeasureRegistry   a  Storage for measures registered with PyPhi.

Users can define custom measures:

Examples:
    >>> @measures.register('ALWAYS_ZERO')  # doctest: +SKIP
    ... def always_zero(a, b):
    ...    return 0

And use them by setting ``config.MEASURE = 'ALWAYS_ZERO'``.
measuresc                 0   > [         TU ]  5         / U l        g N)super__init___asymmetricself	__class__s    H/home/james-whalen/.local/lib/python3.13/site-packages/pyphi/distance.pyr   MeasureRegistry.__init__*   s        c                    ^ ^^ UUU 4S jnU$ )zDecorator for registering a measure with PyPhi.

Args:
    name (string): The name of the measure.

Keyword Args:
    asymmetric (boolean): ``True`` if the measure is asymmetric.
c                 j   > T(       a  TR                   R                  T5        U TR                  T'   U $ r   )r   appendstore)func
asymmetricnamer   s    r   register_func/MeasureRegistry.register.<locals>.register_func7   s-      ''-#DJJtKr     )r   r'   r&   r(   s   ``` r   registerMeasureRegistry.register.   s    	
 r    c                     U R                   $ )z%Return a list of asymmetric measures.r   )r   s    r   r&   MeasureRegistry.asymmetric>   s    r    r.   )F)__name__
__module____qualname____firstlineno____doc__descr   r+   r&   __static_attributes____classcell__r   s   @r   r   r      s#    
 D    r    r   c                   ,   ^  \ rS rSrSrU 4S jrSrU =r$ )np_suppressF   zDecorator to suppress NumPy warnings about divide-by-zero and
multiplication of ``NaN``.

.. note::
    This should only be used in cases where you are *sure* that these
    warnings are not indicative of deeper issues in your code.
c                 "   > [         TU ]  SSS9  g )Nignore)divideinvalid)r   r   r   s    r   r   np_suppress.__init__N   s    (;r    r*   )r0   r1   r2   r3   r4   r   r6   r7   r8   s   @r   r:   r:   F   s    < <r    r:   c                 >    U [         :  a	  [        U    $ [        U 5      $ )a  Return a matrix of Hamming distances for the possible states of |N|
binary nodes.

Args:
    N (int): The number of nodes under consideration

Returns:
    np.ndarray: A |2^N x 2^N| matrix where the |ith| element is the Hamming
    distance between state |i| and state |j|.

Example:
    >>> _hamming_matrix(2)
    array([[0., 1., 1., 2.],
           [1., 0., 2., 1.],
           [1., 2., 0., 1.],
           [2., 1., 1., 0.]])
)!_NUM_PRECOMPUTED_HAMMING_MATRICES_hamming_matrices_compute_hamming_matrix)Ns    r   _hamming_matrixrF   S   s#    $ 	,, ##"1%%r    c                     [         R                  " [        [        R                  " U 5      5      5      n[        XS5      U -  $ )a+  Compute and store a Hamming matrix for |N| nodes.

Hamming matrices have the following sizes::

    N   MBs
    ==  ===
    9   2
    10  8
    11  32
    12  128
    13  512

Given these sizes and the fact that large matrices are needed infrequently,
we store computed matrices using the Joblib filesystem cache instead of
adding computed matrices to the ``_hamming_matrices`` global and clogging
up memory.

This function is only called when |N| >
``_NUM_PRECOMPUTED_HAMMING_MATRICES``. Don't call this function directly;
use |_hamming_matrix| instead.
hamming)nparraylistr   
all_statesr   )rE   possible_statess     r   rD   rD   j   s4    . hhtE$4$4a$9:;O9=AAr    EMDc                     U R                  5       R                  n[        U 5      [        U5      p[        X[	        U5      5      $ )zReturn the Earth Mover's Distance between two distributions (indexed
by state, one dimension per node) using the Hamming distance between states
as the transportation cost function.

Singleton dimensions are sqeezed out.
)squeezendimr   r   rF   )d1d2rE   s      r   hamming_emdrT      s6     	

AR['"+rq)**r    c                 X   ^ ^ [        U U4S j[        T R                  5       5       5      $ )a  Compute the EMD between two effect repertoires.

Because the nodes are independent, the EMD between effect repertoires is
equal to the sum of the EMDs between the marginal distributions of each
node, and the EMD between marginal distribution for a node is the absolute
difference in the probabilities that the node is OFF.

Args:
    d1 (np.ndarray): The first repertoire.
    d2 (np.ndarray): The second repertoire.

Returns:
    float: The EMD between ``d1`` and ``d2``.
c              3   h   >#    U  H'  n[        [        TU5      [        TU5      -
  5      v   M)     g 7fr   )absr   ).0irR   rS   s     r   	<genexpr>effect_emd.<locals>.<genexpr>   s3      (&1 =Q'-A*>>??&s   /2)sumrangerQ   rR   rS   s   ``r   
effect_emdr_      s'      (bgg( ( (r    L1c                 N    [         R                  " X-
  5      R                  5       $ )zReturn the L1 distance between two distributions.

Args:
    d1 (np.ndarray): The first distribution.
    d2 (np.ndarray): The second distribution.

Returns:
    float: The sum of absolute differences of ``d1`` and ``d2``.
)rI   absoluter\   r^   s     r   l1rc      s     ;;rw##%%r    KLDT)r&   c                 D    [        U 5      [        U5      p[        XS5      $ )zReturn the Kullback-Leibler Divergence (KLD) between two distributions.

Args:
    d1 (np.ndarray): The first distribution.
    d2 (np.ndarray): The second distribution.

Returns:
    float: The KLD of ``d1`` from ``d2``.
       @)r   r   r^   s     r   kldrg      s      R['"+23r    ENTROPY_DIFFERENCEc                 h    [        U 5      [        U5      p[        [        U SS9[        USS9-
  5      $ )z;Return the difference in entropy between two distributions.rf   )base)r   rW   r   r^   s     r   entropy_differencerk      s1     R['"+wr$wr'<<==r    PSQ2c                 f    [        U 5      [        U5      pS n[        U" U 5      U" U5      -
  5      $ )z|Compute the PSQ2 measure.

Args:
    d1 (np.ndarray): The first distribution.
    d2 (np.ndarray): The second distribution.
c                     [        U S-  [        R                  " [        R                  " U [	        U 5      -  5      5      -  5      $ )N   )r\   rI   
nan_to_numloglen)ps    r   fpsq2.<locals>.f   s0    AFbmmBFF1s1v:,>??@@r    )r   rW   )rR   rS   rt   s      r   psq2rv      s2     R['"+A ququ}r    MP2Qc           
          [        U 5      [        U5      pS[        U 5      -  n[        U[        R                  " U S-  U-  [        R
                  " X-  5      -  5      -  5      $ )zCompute the MP2Q measure.

Args:
    p (np.ndarray): The unpartitioned repertoire
    q (np.ndarray): The partitioned repertoire
r   ro   )r   rr   r\   rI   rp   rq   )rs   qentropy_dists      r   mp2qr{      sP     1:wqzqs1v:L|bmmQ!VqL266!%=,HIIJJr    KLMBLDc                     [        U 5      [        U5      p[        [        U [        R                  " [        R
                  " X-  5      5      -  5      5      $ )zCompute the KLM divergence.)r   maxrW   rI   rp   rq   )rs   ry   s     r   klmr      s;    
 1:wqzqs1r}}RVVAE]33455r    c                     U [         R                  :X  a  [        nO1U [         R                  :X  a  [        nO[
        R                  " U 5        [        W" X5      [        R                  5      $ )a  Compute the EMD between two repertoires for a given direction.

The full EMD computation is used for cause repertoires. A fast analytic
solution is used for effect repertoires.

Args:
    direction (Direction): |CAUSE| or |EFFECT|.
    d1 (np.ndarray): The first repertoire.
    d2 (np.ndarray): The second repertoire.

Returns:
    float: The EMD between ``d1`` and ``d2``, rounded to |PRECISION|.

Raises:
    ValueError: If ``direction`` is invalid.
)
r   CAUSErT   EFFECTr_   r   	directionroundr	   	PRECISION)r   rR   rS   r%   s       r   directional_emdr      sO    " IOO#	i&&	& 	9%bv//00r    c                     [         R                  S:X  a  [        XU5      nO[        [         R                     " X5      n[	        U[         R
                  5      $ )a)  Compute the distance between two repertoires for the given direction.

Args:
    direction (Direction): |CAUSE| or |EFFECT|.
    r1 (np.ndarray): The first repertoire.
    r2 (np.ndarray): The second repertoire.

Returns:
    float: The distance between ``d1`` and ``d2``, rounded to |PRECISION|.
rN   )r	   MEASUREr   r   r   r   )r   r1r2dists       r   repertoire_distancer     sB     ~~yb1'/v''((r    c                     [         R                  [        R                  5       ;   a(  [	        SR                  [         R                  5      5      e[        [         R                     " X5      $ )zCompute the distance between two repertoires of a system.

Args:
    r1 (np.ndarray): The first repertoire.
    r2 (np.ndarray): The second repertoire.

Returns:
    float: The distance between ``r1`` and ``r2``.
zM{} is asymmetric and cannot be used as a system-level irreducibility measure.)r	   r   r   r&   
ValueErrorformat)r   r   s     r   system_repertoire_distancer   !  sO     ~~,,..&&,fV^^&<> 	> FNN#B++r    )-r4   
contextlibr   numpyrI   pyemdr   scipy.spatial.distancer   scipy.statsr    r   r	   r
   r   r   distributionr   r   registryr   rB   	load_datarC   r   r   errstater:   rF   joblib_memorycacherD   r+   rT   r_   rc   rg   rk   rv   r{   r   r   r   r   r*   r    r   <module>r      s  
 (   (  ; ; 0  %' !OO$6$EG & h & R 	<"++/ 	<&. B B6 
5	+ 	+(& 
4
& 
& 
5T*  +  
'(> )> 
6   
6d+	K  ,	K 
5T*	5T*6  + +618)&,r    