o
    }o™i6  ã                   @   s^   d dl Z e jjdede jfdd„ƒZe jjG dd„ deƒƒZe jjdd	e jd
efdd„ƒZdS )é    NÚindexÚshapec                 C   sT   g }t j|dd}|D ]}| | | ¡ | | } qt  dd„ |D ƒ¡}t j|ddS )a  
    Unravel the index input to fit the given shape.
    This function is needed for torch.jit.script compatibility.

    Args:
        index (int): The index to unravel.
        shape (Tesnor): The shape to unravel the index to.

    Returns:
        Tensor: The unraveled index.
    )r   )Údimsc                 S   s   g | ]}t | ¡ ƒ‘qS © )ÚintÚitem)Ú.0Úxr   r   úg/home/ubuntu/.local/lib/python3.10/site-packages/nemo/collections/asr/parts/utils/optimization_utils.pyÚ
<listcomp>0   s    z!unravel_index.<locals>.<listcomp>)ÚtorchÚflipÚappendÚtensor)r   r   ÚoutÚdimr   r   r
   Úunravel_index   s   
r   c                   @   sv   e Zd ZdZdejfdd„Zdd„ Zdd„ Zd	d
„ Z	de
fdd„Zddede
fdd„Zde
fdd„Zde
fdd„ZdS )ÚLinearSumAssignmentSolvera¶  
    A Solver class for the linear sum assignment (LSA) problem. 
    Designed for torch.jit.script compatibility in NeMo. 
        
    The LSA problem is also referred to as bipartite matching problem. An LSA problem is described 
    by a matrix `cost_mat`, where each cost_mat[i,j] is the cost of matching vertex i of the first partite 
    set (e.g. a "worker") and vertex j of the second set (e.g. a "job"). 
    
    Thus, the goal of LSA-solver is to find a complete assignment of column element to row element with 
    the minimal cost. Note that the solution may not be unique and there could be multiple solutions that 
    yield the same minimal cost.

    LSA problem solver is needed for the following tasks in NeMo: 
        - Permutation Invariant Loss (PIL) for diarization model training
        - Label permutation matching for online speaker diarzation 
        - Concatenated minimum-permutation Word Error Rate (cp-WER) calculation 

    This implementation is based on the LAP solver from scipy: 
        https://github.com/scipy/scipy/blob/v0.18.1/scipy/optimize/_hungarian.py
        The scipy implementation comes with the following license:
    
        Copyright (c) 2008 Brian M. Clapper <bmc@clapper.org>, Gael Varoquaux
        Author: Brian M. Clapper, Gael Varoquaux
        License: 3-clause BSD

    References
        1. http://csclab.murraystate.edu/bob.pilgrim/445/munkres.html
        2. https://en.wikipedia.org/wiki/Hungarian_algorithm
        3. https://github.com/scipy/scipy/blob/v0.18.1/scipy/optimize/_hungarian.py


    Attributes:
        cost_mat (Tensor): 2D matrix containing cost matrix. Number of columns must be larger than number of rows.
        row_uncovered (Tensor): 1D matrix containing boolean values indicating whether a row is covered.
        col_uncovered (Tensor): 1D matrix containing boolean values indicating whether a column is covered.
        zero_row (Tensor): 1D matrix containing the row index of the last zero found.
        zero_col (Tensor): 1D matrix containing the column index of the last zero found.
        path (Tensor): 2D matrix containing the path taken through the matrix.
        marked (Tensor): 2D matrix containing the marked zeros.
    Úcost_matrixc                 C   s¾   || _ | j j\}}tjdtjd |j¡| _tjdtjd |j¡| _tj	|tj
d |j¡| _tj	|tj
d |j¡| _tj|| dftjd |j¡| _tj||ftjd |j¡| _d S )Nr   )Údtypeé   )Úcost_matr   r   r   ÚlongÚtoÚdeviceÚzero_rowÚzero_colÚonesÚboolÚrow_uncoveredÚcol_uncoveredÚzerosÚpathÚmarked)Úselfr   Úrow_lenÚcol_lenr   r   r
   Ú__init___   s   ""z"LinearSumAssignmentSolver.__init__c                 C   s    d| j dd…< d| jdd…< dS )z]
        Clear all covered matrix cells and assign `True` to all uncovered elements.
        TN)r   r    ©r$   r   r   r
   Ú_reset_uncovered_matp   s   z.LinearSumAssignmentSolver._reset_uncovered_matc                 C   s(   |  j tj| j ddd  d¡8  _ dS )aÚ  
        Step 1

        Goal: Subtract the smallest element of each row from its elements.
            - All elements of the matrix are now non-negative.
            - Therefore, an assignment of total cost 0 is the minimum cost assignment.
            - This operation leads to at least one zero in each row.

        Procedure:
        - For each row of the matrix, find the smallest element and subtract it from every element in its row.
        - Go to Step 2.
        é   ©r   r   r   )r   r   ÚminÚ	unsqueezer(   r   r   r
   Ú_step1w   s   $z LinearSumAssignmentSolver._step1c                 C   s€   t  | jdk¡}t|d ƒt|d ƒ}}t||ƒD ]\}}| j| r9| j| r9d| j||f< d| j|< d| j|< q|  ¡  dS )a8  
        Step 2

        Goal: Make sure assignment with cost sum 0 is feasible.

        Procedure:
        - Find a zero in the resulting cost matrix. 
        - If there are no marked zeros in its row or column, mark the zero. 
        - Repeat for each element in the matrix.
        - Go to step 3.
        r   r*   Fé   )	r   Úwherer   ÚlistÚzipr    r   r#   r)   )r$   Úind_outÚindÚvalÚiÚjr   r   r
   Ú_step2‡   s   

€z LinearSumAssignmentSolver._step2Úreturnc                 C   s:   | j dk}d| jtj|dd< | ¡ | jjd k rdS dS )a¬  
        Step 3
        
        Goal: All zeros in the matrix must be covered by marking with the least numbers of rows and columns.

        Procedure:
            - Cover each column containing a marked zero. 
                - If n columns are covered, the marked zeros describe a complete set of unique assignments.
                In this case, Go to Step 0 (Done state)
                - Otherwise, Go to Step 4.
        r*   Fr   r+   é   )r#   r    r   ÚanyÚsumr   r   )r$   r#   r   r   r
   Ú_step3ž   s
   
z LinearSumAssignmentSolver._step3FÚbypassc           
      C   s.  | j dk ¡ }|| j d¡ }|| j ¡ 9 }| j j\}}|s•	 tt 	|¡ 
¡ t ||g¡ƒ}t|d  
¡ ƒt|d  
¡ ƒ}}|||f dkrIdS d| j||f< t 	| j| dk ¡ ¡}	| j||	f dkrst |¡| _t |¡| _dS |	}d| j|< d| j|< |dd…|f | j |dd…|f< d||< qdS )	aò  
        Step 4

        Goal: Cover all columns containing a marked zero.

        Procedure:
        - Find a non-covered zero and put a prime mark on it. 
            - If there is no marked zero in the row containing this primed zero, Go to Step 5.
            - Otherwise, cover this row and uncover the column containing the marked zero. 
        - Continue in this manner until there are no uncovered zeros left. 
        - Save the smallest uncovered value.
        - Go to Step 6.
        r   r*   Té   r   é   FN)r   r   r   r-   r    r   r   r   r   Úargmaxr   r   r#   r   r   )
r$   r>   r   Úcovered_cost_matr%   r&   ÚurvÚrowÚcolÚmark_colr   r   r
   Ú_step4±   s.   "

"ïz LinearSumAssignmentSolver._step4c                 C   s¤  t  d¡}| j}| j ¡ ||df< | j ¡ ||df< 	 t  | jdd…||df f dk ¡ ¡}| j|||df f dkr=nJ|d7 }|||df< ||d df ||df< tt  | j||df  dk ¡ ¡ƒ}| j||f dkrpd}|d7 }||d df ||df< |||df< qt	t| 
¡ ƒd ƒD ]2}| j||df ||df f dkr´d| j||df ||df f< q‘d| j||df ||df f< q‘|  ¡  d| j| jdk< dS )a  
        Step 5

        Goal: Construct a series of alternating primed and marked zeros as follows.
        
        Procedure:
        - Let Z0 represent the uncovered primed zero found in Step 4.
        - Let Z1 denote the marked zero in the column of Z0 (if any).
        - Let Z2 denote the primed zero in the row of Z1 (there will always be one).
        - Continue until the series terminates at a primed zero that has no marked zero in its column. 
        - Unmark each marked zero of the series.
        - Mark each primed zero of the series.
        - Erase all primes and uncover every line in the matrix. 
        - Return to Step 3
        r   r*   TNr   éÿÿÿÿr/   )r   r   r"   r   r   r   rA   r#   r   Úranger   r)   )r$   Úcountr"   rD   rE   r6   r   r   r
   Ú_step5Ù   s2   
($î"  z LinearSumAssignmentSolver._step5c                 C   sx   t  | j¡r:t  | j¡r:t j| j| j ddd }t  || j ¡}| j| j   |7  < | jdd…| jf  |8  < dS )aY  
        Step 6

        Goal: Prepare for another iteration by modifying the cost matrix.

        Procedure:
        - Add the value found in Step 4 to every element of each covered row.
        - Subtract it from every element of each uncovered column.
        - Return to Step 4 without altering any marks, primes, or covered lines.
        r   r+   Nr:   )r   r;   r   r    r,   r   )r$   Ú
row_minvalÚminvalr   r   r
   Ú_step6  s   z LinearSumAssignmentSolver._step6N)F)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   ÚTensorr'   r)   r.   r8   r   r=   r   rG   rK   rN   r   r   r   r
   r   4   s    )(6r   éd   r   Úmax_sizec                 C   s>  |   ¡  ¡ } t| jƒdkrtd| j› dƒ‚t| jƒ|kr,td| j› d|› d|› dƒ‚| jd | jd	 k r<| j} d
}nd}t| ƒ}d	| jv rId	nd}|d	krˆ|dkrX| ¡ }n,|dkra| 	¡ }n#|dkrj| 
¡ }n|dkrs| ¡ }n|dkr|| ¡ }n|dkr„| ¡ }|d	ksO|r|jj}n|j}t |dk¡\}}||fS )aQ  
    Launch the linear sum assignment algorithm on a cost matrix.

    Args:
        cost_matrix (Tensor): The cost matrix of shape (N, M) where M should be larger than N.

    Returns:
        row_index (Tensor): The row indices of the optimal assignments.
        col_index (Tensor): The column indices of the optimal assignments.
    r   z!2-d tensor is expected but got a z tensorzCost matrix size z- is too large. The maximum supported size is r	   Ú.r*   r   TFr/   r:   r@   r?   )ÚcloneÚdetachÚlenr   Ú
ValueErrorÚmaxÚTr   r.   r8   r=   rG   rK   rN   r#   r   r0   )r   rU   Ú
transposedÚ
lap_solverÚf_intr#   Ú	row_indexÚ	col_indexr   r   r
   Úlinear_sum_assignment"  s@   ÿ




ô
rb   )rT   )	r   ÚjitÚscriptr   rS   r   Úobjectr   rb   r   r   r   r
   Ú<module>   s    n