#### Circular Convolution

S
```# https://en.wikipedia.org/wiki/Circular_convolution

"""
Circular convolution, also known as cyclic convolution,
is a special case of periodic convolution, which is the convolution of two
periodic functions that have the same period. Periodic convolution arises,
for example, in the context of the discrete-time Fourier transform (DTFT).
In particular, the DTFT of the product of two discrete sequences is the periodic
convolution of the DTFTs of the individual sequences. And each DTFT is a periodic
summation of a continuous Fourier transform function.

Source: https://en.wikipedia.org/wiki/Circular_convolution
"""

import doctest
from collections import deque

import numpy as np

class CircularConvolution:
"""
This class stores the first and second signal and performs the circular convolution
"""

def __init__(self) -> None:
"""
First signal and second signal are stored as 1-D array
"""

self.first_signal = [2, 1, 2, -1]
self.second_signal = [1, 2, 3, 4]

def circular_convolution(self) -> list[float]:
"""
This function performs the circular convolution of the first and second signal
using matrix method

Usage:
>>> import circular_convolution as cc
>>> convolution = cc.CircularConvolution()
>>> convolution.circular_convolution()
[10, 10, 6, 14]

>>> convolution.first_signal = [0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6]
>>> convolution.second_signal = [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3, 1.5]
>>> convolution.circular_convolution()
[5.2, 6.0, 6.48, 6.64, 6.48, 6.0, 5.2, 4.08]

>>> convolution.first_signal = [-1, 1, 2, -2]
>>> convolution.second_signal = [0.5, 1, -1, 2, 0.75]
>>> convolution.circular_convolution()
[6.25, -3.0, 1.5, -2.0, -2.75]

>>> convolution.first_signal = [1, -1, 2, 3, -1]
>>> convolution.second_signal = [1, 2, 3]
>>> convolution.circular_convolution()
[8, -2, 3, 4, 11]

"""

length_first_signal = len(self.first_signal)
length_second_signal = len(self.second_signal)

max_length = max(length_first_signal, length_second_signal)

# create a zero matrix of max_length x max_length
matrix = [ * max_length for i in range(max_length)]

# fills the smaller signal with zeros to make both signals of same length
if length_first_signal < length_second_signal:
self.first_signal +=  * (max_length - length_first_signal)
elif length_first_signal > length_second_signal:
self.second_signal +=  * (max_length - length_second_signal)

"""
Fills the matrix in the following way assuming 'x' is the signal of length 4
[
[x, x, x, x],
[x, x, x, x],
[x, x, x, x],
[x, x, x, x]
]
"""
for i in range(max_length):
rotated_signal = deque(self.second_signal)
rotated_signal.rotate(i)
for j, item in enumerate(rotated_signal):
matrix[i][j] += item

# multiply the matrix with the first signal
final_signal = np.matmul(np.transpose(matrix), np.transpose(self.first_signal))

# rounding-off to two decimal places
return [round(i, 2) for i in final_signal]

if __name__ == "__main__":
doctest.testmod()
```  