o
    ép©iJ  ã                   @   sT   d dl mZ d dlmZmZmZmZ d dlmZ dej	deeef defdd„Z
dS )	é    )Úpartial)ÚCallableÚDictÚSetÚTextNÚtrunkÚbranchesÚreturnc                    s  ‡ fdd„}t ˆ dƒr|ƒ  g ˆ _‡ fdd„}ˆ  |¡}ˆ j |¡ ‡ fdd„}t|tƒs4dd	„ |D ƒ}tƒ }| ¡ D ]\}}||vrHtƒ ||< ||  |¡ q;ˆ  	¡ D ]\}}	||vr]qT|| D ]}|	 
t||ƒ¡}ˆ j |¡ qaqT‡ fd
d„}
ˆ  
|
¡}ˆ j |¡ |S )a.  Add probing branches to a trunk module

    Parameters
    ----------
    trunk : nn.Module
        Multi-layer trunk.
    branches : {branch_name: layer_name} dict or [layer_name] list
        Indicate where to plug a probing branch.

    Returns
    -------
    revert : Callable
        Callable that, when called, removes probing branches.

    Usage
    -----

    Define a trunk made out of three consecutive layers

    >>> import torch.nn as nn
    >>> class Trunk(nn.Module):
    ...
    ...     def __init__(self):
    ...         super().__init__()
    ...         self.layer1 = nn.Linear(1, 2)
    ...         self.layer2 = nn.Linear(2, 3)
    ...         self.layer3 = nn.Linear(3, 4)
    ...
    ...     def forward(self, x):
    ...         return self.layer3(self.layer2(self.layer1(x)))

    >>> trunk = Trunk()
    >>> x = torch.tensor((0.,))
    >>> trunk(x)
    # tensor([ 0.4548, -0.1814,  0.9494,  1.0445], grad_fn=<AddBackward0>)

    Add two probing branches:
    - first one is called "probe1" and probes the output of "layer1"
    - second one is called "probe2" and probes the output of "layer3"

    >>> revert = probe(trunk, {"probe1": "layer1", "probe2": "layer3"})
    >>> trunk(x)
    # {'probe1': tensor([ 0.5854, -0.9685], grad_fn=<AddBackward0>),
    #  'probe2': tensor([ 0.4548, -0.1814,  0.9494,  1.0445], grad_fn=<AddBackward0>)}

    Use callback returned by `probe` to revert its effect

    >>> revert()
    >>> trunk(x)
    # tensor([ 0.4548, -0.1814,  0.9494,  1.0445], grad_fn=<AddBackward0>)

    For convenience, one can also define probes as a list of layers:

    >>> revert = probe(trunk, ['layer1', 'layer3'])
    >>> trunk(x)
    # {'layer1': tensor([ 0.5854, -0.9685], grad_fn=<AddBackward0>),
    #  'layer3': tensor([ 0.4548, -0.1814,  0.9494,  1.0445], grad_fn=<AddBackward0>)}
    c                     s    ˆ ` ˆ jD ]} |  ¡  qˆ `d S ©N)Ú__probeÚ__probe_handlesÚremove)Úhandle©r   © úN/home/ubuntu/.local/lib/python3.10/site-packages/pyannote/audio/utils/probe.pyr   Z   s   

zprobe.<locals>.remover   c                    s   t ƒ ˆ _d S r
   )Údictr   )ÚmoduleÚinputr   r   r   Ú__probe_inite   s   zprobe.<locals>.__probe_initc                    s   |ˆ j | < d S r
   ©r   )Úbranch_namer   r   Úoutputr   r   r   Ú__probe_appendk   s   zprobe.<locals>.__probe_appendc                 S   s   i | ]}||“qS r   r   )Ú.0Úbr   r   r   Ú
<dictcomp>o   s    zprobe.<locals>.<dictcomp>c                    s   ˆ j S r
   r   )r   r   r   r   r   r   Ú__probe_return~   s   zprobe.<locals>.__probe_return)Úhasattrr   Úregister_forward_pre_hookÚappendÚ
isinstancer   ÚitemsÚsetÚaddÚnamed_modulesÚregister_forward_hookr   )r   r   r   r   r   r   Úsehcnarbr   Ú
layer_nameÚlayerr   r   r   r   Úprobe   s4   <



þ
r*   )Ú	functoolsr   Útypingr   r   r   r   Útorch.nnÚnnÚModuler*   r   r   r   r   Ú<module>   s   $