o
    %ݫis                  
   @   s   d Z ddlZddlZddlZddlm  mZ ddl	m
Z
 ddlmZ G dd dejjZdd Zd	d
 Zdededededef
ddZdededededef
ddZ	d"ddZd#ddZdd Zdd Zdd Zd d! ZdS )$a  This library implements different operations needed by quaternion-
valued architectures.
This work is inspired by:
"Quaternion neural networks" - Parcollet T.
"Quaternion recurrent neural networks" - Parcollet T. et al.
"Quaternion convolutional neural networks for end-to-end automatic speech
recognition" - Parcollet T. et al.
"Deep quaternion networks" - Gaudet Chase J. et al.

Authors
 * Titouan Parcollet 2020
    N)chi)Variablec                   @   s(   e Zd ZdZedd Zedd ZdS )QuaternionLinearCustomBackwarda"  This class redefine the backpropagation of a quaternion linear layer
    (not a spinor layer). By doing so, we can save up to 4x memory, but it
    is also 2x slower than 'quaternion_linear_op'. It should be used
    within speechbrain.nnet.quaternion_networks.linear.QuaternionLinear.
    c                 C   s   |  |||||| tj|| | | gdd}tj||| |gdd}tj|||| gdd}	tj|| ||gdd}
tj|||	|
gdd}|jrQt|||S t||S )a  
        Applies a quaternion linear transformation to the incoming data:
        It is important to notice that the forward phase of a QNN is defined
        as W * Inputs (with * equal to the Hamilton product). The constructed
        cat_kernels_4_quaternion is a modified version of the quaternion
        representation so when we do torch.mm(Input,W) it's equivalent
        to W * Inputs.

        Arguments
        ---------
        ctx : PyTorch context object
            Used to save the context necessary to perform a backwards pass.
        input : torch.Tensor
            Quaternion input tensor to be transformed. Shape: [batch*time, X].
        r_weight : torch.Parameter
            Real part of the quaternion weight matrix of this layer.
        i_weight : torch.Parameter
            First imaginary part of the quaternion weight matrix of this layer.
        j_weight : torch.Parameter
            Second imaginary part of the quaternion weight matrix of this layer.
        k_weight : torch.Parameter
            Third imaginary part of the quaternion weight matrix of this layer.
        bias : torch.Parameter

        Returns
        -------
        The linearly transformed quaternions
        r   dim   )save_for_backwardtorchcatrequires_gradaddmmmm)ctxinputr_weighti_weightj_weightk_weightbiascat_kernels_4_rcat_kernels_4_icat_kernels_4_jcat_kernels_4_kcat_kernels_4_quaternion r   ^/home/ubuntu/.local/lib/python3.10/site-packages/speechbrain/nnet/quaternion_networks/q_ops.pyforward   s4   	z&QuaternionLinearCustomBackward.forwardc                 C   s\  | j \}}}}}}d } }	 }
 } }}tj|| | | gdd}tj||| |gdd}tj|||| gdd}tj|| ||gdd}ttj||||gdddddd}| d }|dd|d }|d|d |d }|d|d	 |d }|d||d  |d }tj|| | | gdd}tj||| |gdd}tj|||| gdd}tj|| ||gdd}ttj||||gdddd}| d }|dd|d }|d|d |d }|d|d	 |d }|d||d  |d }tj||||gdd}tj| ||| gdd}tj| | ||gdd}tj| || |gdd}tj||||gdd}| jd rG||}| jd r|dd|dd}|d}|d}|dd|dd|}	|dd|d||}
|dd|d|d	 |}|dd|d|d
 |}| jd r|	d
d}||	|
|||fS )a  
        Run the backward phase of the forward call defined above. This
        implementation follows the quaternion backpropagation of a quaternion
        layer that can be found in "Quaternion neural networks" - Parcollet T.
        Page 48.

        Arguments
        ---------
        ctx : Pytorch context object
            Contains saved weights and bias
        grad_output : torch.Tensor
            The output of the forward part

        Returns
        -------
        The corresponding gradients of this op
        Nr   r   r   F)r               )saved_tensorsr	   r
   r   permutesizenarrowneeds_input_gradr   sumsqueeze)r   grad_outputr   r   r   r   r   r   
grad_inputgrad_weight_rgrad_weight_igrad_weight_jgrad_weight_k	grad_biasinput_rinput_iinput_jinput_kcat_kernels_4_quaternion_T	nb_hiddenrijk	input_matgrad_matgrad_weightunit_size_xunit_size_yr   r   r   backward\   s   




z'QuaternionLinearCustomBackward.backwardN)__name__
__module____qualname____doc__staticmethodr   r?   r   r   r   r   r      s    
=r   c                 C   s   t j|| | | gdd}t j||| |gdd}t j|||| gdd}t j|| ||gdd}	t j||||	gdd}
|  dkrS|jrMt || |
S t | |
S t | |
}|jr`|| S |S )a  
    Applies a quaternion linear transformation to the incoming data:
    It is important to notice that the forward phase of a QNN is defined
    as W * Inputs (with * equal to the Hamilton product). The constructed
    cat_kernels_4_quaternion is a modified version of the quaternion
    representation so when we do torch.mm(Input,W) it's equivalent
    to W * Inputs.

    Arguments
    ---------
    input : torch.Tensor
        Quaternion input tensor to be transformed.
    r_weight : torch.Parameter
        Real part of the quaternion weight matrix of this layer.
    i_weight : torch.Parameter
        First imaginary part of the quaternion weight matrix of this layer.
    j_weight : torch.Parameter
        Second imaginary part of the quaternion weight matrix of this layer.
    k_weight : torch.Parameter
        Third imaginary part of the quaternion weight matrix of this layer.
    bias : torch.Parameter

    Returns
    -------
    The linearly transformed quaternions
    r   r   r   r   )r	   r
   r   r   r   r   matmul)r   r   r   r   r   r   r   r   r   r   r   outputr   r   r   quaternion_linear_op   s0   
rG   c                 C   sV  || }|| }	|| }
|| }t ||	 |
 | d }|| }|| }|| }|| }d}|||  }	|||  }
|||  }|| | }|| | }|| | }|| | }|| | }|| | }|jrt j||d|
|   |||  |||  gdd}t j||||  |d|	|   |||  gdd}t j||||  |||  |d|	|
   gdd}n9t j|d|
|  || || gdd}t j||| d|	|  || gdd}t j||| || d|	|
  gdd}t j||||gdd}t j||||gdd}|  dkr|jrt || |S t | |S t | |}|jr)|| S |S )a6  
    Applies a quaternion rotation transformation to the incoming data:
    The rotation W*x*W^t can be replaced by R*x following:
    https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
    Works for unitary and non-unitary weights (they will be normalized).
    The initial size of the input must be a multiple of 4 with the real part
    equal to zero. Rotations only affect the vector part of a quaternion.

    Arguments
    ---------
    input : torch.Tensor
        Quaternion input tensor to be transformed.
    r_weight : torch.Parameter
        Real part of the quaternion weight matrix of this layer.
    i_weight : torch.Parameter
        First imaginary part of the quaternion weight matrix of this layer.
    j_weight : torch.Parameter
        Second imaginary part of the quaternion weight matrix of this layer.
    k_weight : torch.Parameter
        Third imaginary part of the quaternion weight matrix of this layer.
    bias : torch.Parameter
    scale : torch.Parameter
        In the context of a spinor neural network, multiple rotations of
        the input vector x are performed and summed. Hence, the norm of
        the output vector always increases with the number of layers, making
        the neural network instable with deep configurations. The scale
        parameters are learnable parameters that acts like gates by multiplying
        the output vector with a small trainable parameter.
    zero_kernel : torch.Parameter
        The zero kernel is simply a tensor of zeros with require grad = False.
        Its shape is equivalent to a quaternion component shape. In fact,
        it is only needed to make the dimensions match when using the rotation
        matrix : https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation

    Returns
    -------
    The linearly rotated quaternions
    -C6?       @      ?r   r   r   r   )r	   sqrtr   r
   r   r   r   rE   )r   r   r   r   r   r   scalezero_kernelsquare_rsquare_isquare_jsquare_knorm
r_n_weight
i_n_weight
j_n_weight
k_n_weightnorm_factorrirjrkijikjkrot_kernel_1rot_kernel_2rot_kernel_3zero_kernel2global_rot_kernelrF   r   r   r   quaternion_linear_rotation_op   s   ,

	

	


rc   stridepaddinggroupsdilationconv1dc           "   	   C   s<  || }|| }|| }|| }t || | | d }|| }|| }|| }|| }d}|||  }|||  }|||  }|| | }|| | }|| | }|| | }|| | }|| | }|jrt j||d||   |||  |||  gdd}t j||||  |d||   |||  gdd}t j||||  |||  |d||   gdd}n9t j|d||  || || gdd}t j||| d||  || gdd}t j||| || d||  gdd}t j||||gdd} t j| |||gdd}!|rtj| |!|||	||
dS tj| |!|||	||
dS )a	  
    Applies a quaternion rotation transformation to the incoming data:
    The rotation W*x*W^t can be replaced by R*x following:
    https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
    Works for unitary and non-unitary weights (they will be normalized).
    The initial size of the input must be a multiple of 4 with the real part
    equal to zero. Rotations only affect the vector part of a quaternion.

    Arguments
    ---------
    input : torch.Tensor
        Quaternion input tensor to be transformed.
    r_weight : torch.Parameter
        Real part of the quaternion weight matrix of this layer.
    i_weight : torch.Parameter
        First imaginary part of the quaternion weight matrix of this layer.
    j_weight : torch.Parameter
        Second imaginary part of the quaternion weight matrix of this layer.
    k_weight : torch.Parameter
        Third imaginary part of the quaternion weight matrix of this layer.
    bias : torch.Parameter
    scale : torch.Parameter
        In the context of a spinor neural network, multiple rotations of
        the input vector x are performed and summed. Hence, the norm of
        the output vector always increases with the number of layers, making
        the neural network instable with deep configurations. The scale
        parameters are learnable parameters that acts like gates by multiplying
        the output vector with a small trainable parameter.
    zero_kernel : torch.Parameter
        The zero kernel is simply a tensor of zeros with require grad = False.
        Its shape is equivalent to a quaternion component shape. In fact,
        it is only needed to make the dimensions match when using the rotation
        matrix : https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
    stride : int
        Stride factor of the convolutional filters.
    padding : int
        Amount of padding. See torch.nn documentation for more information.
    groups : int
        This option specifies the convolutional groups. See torch.nn
        documentation for more information.
    dilation : int
        Dilation factor of the convolutional filters.
    conv1d : bool
        If true, a 1D convolution operation will be applied. Otherwise, a 2D
        convolution is called.

    Returns
    -------
    The rotated quaternion inputs
    rH   rI   rJ   r   r   r   r   weightr   rd   re   rg   rf   )r	   rK   r   r
   Frh   conv2d)"r   r   r   r   r   r   rL   rM   rd   re   rf   rg   rh   rN   rO   rP   rQ   rR   rS   rT   rU   rV   rW   rX   rY   rZ   r[   r\   r]   r^   r_   r`   ra   rb   r   r   r   quaternion_conv_rotation_opy  s   B

	

	



rm   c              	   C   s   t j|| | | gdd}t j||| |gdd}t j|||| gdd}t j|| ||gdd}t j||||gdd}|
rKtj| |||||	|dS tj| |||||	|dS )a  
    Applies a quaternion convolution transformation to the incoming data:
    It is important to notice that the forward phase of a QCNN is defined
    as W * Inputs (with * equal to the Hamilton product). The constructed
    cat_kernels_4_quaternion is a modified version of the quaternion
    representation so when we do torch.mm(Input,W) it's equivalent
    to W * Inputs.

    Arguments
    ---------
    input : torch.Tensor
        Quaternion input tensor to be transformed.
    r_weight : torch.Parameter
        Real part of the quaternion weight matrix of this layer.
    i_weight : torch.Parameter
        First imaginary part of the quaternion weight matrix of this layer.
    j_weight : torch.Parameter
        Second imaginary part of the quaternion weight matrix of this layer.
    k_weight : torch.Parameter
        Third imaginary part of the quaternion weight matrix of this layer.
    bias : torch.Parameter
    stride : int
        Stride factor of the convolutional filters.
    padding : int
        Amount of padding. See torch.nn documentation for more information.
    groups : int
        This option specifies the convolutional groups. See torch.nn
        documentation for more information.
    dilation : int
        Dilation factor of the convolutional filters.
    conv1d : bool
        If true, a 1D convolution operation will be applied. Otherwise, a 2D
        convolution is called.

    Returns
    -------
    The convolved quaternion inputs
    r   r   r   ri   )r	   r
   rk   rh   rl   )r   r   r   r   r   r   rd   re   rf   rg   rh   r   r   r   r   r   r   r   r   quaternion_conv_op  sF   4

rn   glorotc                 C   s  t jjt d d |durt |}| | }|| }n| }|}|dkr1dt d||   }n	dt d|  }|du rC| |f}nt|tu rS|| ft	|f }n	|| fg |R  }t
tjdd||d	}	t |}
t|
d
d}t|
d
d}t|
d
d}td|
D ]1}t|| d || d  || d  d }||  |  < ||  |  < ||  |  < q||}||}||}t|tj tj}|	t| }|	| t| }|	| t| }|	| t| }||||fS )a   Returns a matrix of quaternion numbers initialized with the method
    described in "Quaternion Recurrent Neural Network " - Parcollet T.

    Arguments
    ---------
    in_features : int
        Number of real values of the input layer (quaternion // 4).
    out_features : int
        Number of real values of the output layer (quaternion // 4).
    kernel_size : int
        Kernel_size for convolutional layers (ex: (3,3)).
    criterion : str
        (glorot, he)

    Returns
    -------
    Matrix of initialized quaternion numbers
    i)seedNro   rJ   r   r   r   )locrL   r$   r   r   rH   )nprandomrp   r	   initial_seedprodrK   typeinttuple
from_numpyr   rvsFloatTensoruniform_rangereshaperandmathpicossin)in_featuresout_featureskernel_size	criterionreceptive_fieldfan_infan_outskernel_shapemodulusnumber_of_weightsv_iv_jv_kr7   rR   phaseweight_rweight_iweight_jweight_kr   r   r   quaternion_inity  sD   



.


r   hec                 C   sT  |du r	| |f}nt |tu r|| ft|f }n	|| fg |R  }t|}t|dd}t|dd}t|dd}t|dd}	td|D ]?}
t	||
 d ||
 d  ||
 d  |	|
 d  d }||
  |  < ||
  |  < ||
  |  < |	|
  |  < qP|
|}|
|}|
|}|	
|}	||||	fS )a  Returns a matrix of unitary quaternion numbers.

    Arguments
    ---------
    in_features : int
        Number of real values of the input layer (quaternion // 4).
    out_features : int
        Number of real values of the output layer (quaternion // 4).
    kernel_size : int
        Kernel_size for convolutional layers (ex: (3,3)).
    criterion : str
        (glorot, he)

    Returns
    -------
    Matrix of unitary quaternion numbers.
    Nr   r   r   r   rH   )rv   rw   rx   rr   ru   r	   r{   r|   r}   rK   r~   )r   r   r   r   r   r   v_rr   r   r   r7   rR   r   r   r   unitary_init  s0   

4



r   c           
      C   s^   ||  d|  dd|\}}}}	|| j| _||j|_||j|_|	|j|_dS )a  Applies the weight initialization function given to the parameters.

    Arguments
    ---------
    r_weight : torch.Parameters
        (nb_quaternion_in, nb_quaternion_out)
    i_weight : torch.Parameters
        (nb_quaternion_in, nb_quaternion_out)
    j_weight : torch.Parameters
        (nb_quaternion_in, nb_quaternion_out)
    k_weight : torch.Parameters
        (nb_quaternion_in, nb_quaternion_out)
    init_func : function
        (unitary_init, quaternion_init)
    init_criterion : str
        (glorot, he)
    r   r   Nr$   type_asdata)
r   r   r   r   	init_funcinit_criterionr6   r7   r8   r9   r   r   r   affect_init  s   r   c                 C   sh   |  d}|  d}|||||d\}	}
}}|	| j| _|
|j|_||j|_||j|_dS )ar  Applies the weight initialization function given to the parameters.
    This is specifically written for convolutional layers.

    Arguments
    ---------
    r_weight : torch.Parameters
        (nb_quaternion_in, nb_quaternion_out)
    i_weight : torch.Parameters
        (nb_quaternion_in, nb_quaternion_out)
    j_weight : torch.Parameters
        (nb_quaternion_in, nb_quaternion_out)
    k_weight : torch.Parameters
        (nb_quaternion_in, nb_quaternion_out)
    kernel_size : int
        Kernel size.
    init_func : function
        (unitary_init, quaternion_init)
    init_criterion : str
        (glorot, he)
    r   r   )r   r   Nr   )r   r   r   r   r   r   r   in_channelsout_channelsr6   r7   r8   r9   r   r   r   affect_conv_init  s   

r   c                 C   sH   t | dvrtdtt  | d }|d dkr"tdt| dS )zCheck the quaternion-valued shape for a linear layer.

    Arguments
    ---------
    input_shape : tuple
        Expected shape of the input.
    >   r   r   r    zFQuaternion linear accepts only input of dimension 2 or 3. input.dim = r   r   r   zPQuaternion torch.Tensors must have dimensions divisible by 4. input.size()[1] = N)len	Exceptionstrr   r   )input_shaper5   r   r   r   check_quaternion_inputB  s   	
r   c                 C   s   t | jd |jd  |jd  |jd  }t j|dd|d}|| }|  j|9  _| j|9  _| j|9  _| j|9  _dS )a.  Renorms the magnitude of the quaternion-valued weights.

    Arguments
    ---------
    r_weight : torch.Parameter
    i_weight : torch.Parameter
    j_weight : torch.Parameter
    k_weight : torch.Parameter
    max_norm : float
        The maximum norm of the magnitude of the quaternion weights
    r   r   )pr   maxnormN)r	   rK   r   renorm)r   r   r   r   max_normweight_magnituderenormed_weight_magnitudefactorr   r   r   !renorm_quaternion_weights_inplaceZ  s"   r   )Nro   )Nr   )rC   r   numpyrr   r	   torch.nn.functionalnn
functionalrk   scipy.statsr   torch.autogradr   autogradFunctionr   rG   rc   rw   boolrm   rn   r   r   r   r   r   r   r   r   r   r   <module>   sT     !; 	

 %	

]

L3+