In this notebook, we will illustrate the convolution operation. The convolution of two signals is a fundamental operation in signal processing. Mainly, because the output of any linear time-invariant (LTI) system is given by the convolution of its impulse response with the input signal. Another important application of convolution is the convolution theorem, which states that multiplication in time-domain corresponds to convolution in frequency domain and vice versa.

In this notebook, we will illustrate the operation of convolution and how we can calculate it numerically. Formally, the convolution $(f_1*f_2)(t)$ of two signals $f_1(t)$ and $f_2(t)$ is defined by the convolution integral

$$ (f_1*f_2)(t) = \int_{-\infty}^{\infty}f_1(\tau)f_2(t-\tau)d\tau. $$So, the convolution of two function is the integral over the product of both functions, where one function is time-shifted and flipped in time. Let us not think about why this operation makes sense for now. Instead, let's define two functions $f_1(t)$ and $f_2(t)$:

```
f1 = lambda t: np.maximum(0, 1-abs(t))
f2 = lambda t: (t>0) * np.exp(-2*t)
```

Let's plot these two functions to see how they look like:

```
Fs = 50 # our sampling frequency for the plotting
T = 5 # the time range we are interested in
t = np.arange(-T, T, 1/Fs) # the time samples
```

```
plt.plot(t, f1(t), label='$f_1(t)$')
plt.plot(t, f2(t), label='$f_2(t)$')
```

```
t0 = 1
flipped = lambda tau: f2(t0-tau)
product = lambda tau: f1(tau)*f2(t0-tau)
plt.figure(figsize=(8,3))
plt.plot(t, f1(t), label=r'$f_1(\tau)$')
plt.plot(t, flipped(t), label=r'$f_2(t_0-\tau)$')
plt.plot(t, product(t), label=r'$f_1(\tau)f_2(t_0-\tau)$')
# Explicitely calculate the integral, using the Simpson integration rule
display(HTML("Result of the convolution (red shaded area): $(f_1*f_2)(t_0=%.0f) = %.2f$" % (t0, scipy.integrate.simps(product(t), t))))
```

As we see, the green curve $f_2(t_0-\tau)$ was shifted by $t_0$ to the right, and then horizontally mirrored. The value of the convolution integral corresponds to the red shaded area, which is the product of both curves.

We can now write a small function which illustrates the convolution integral for different time-shifts.

```
def showConvolution(f1, f2, t0):
# Calculate the overall convolution result using Simpson integration
convolution = np.zeros(len(t))
for n, t_ in enumerate(t):
prod = lambda tau: f1(tau) * f2(t_-tau)
convolution[n] = scipy.integrate.simps(prod(t), t)
# Create the shifted and flipped function
f_shift = lambda t: f2(t0-t)
prod = lambda tau: f1(tau) * f2(t0-tau)
# Plot the curves
plt.subplot(211)
plt.plot(t, f1(t), label=r'$f_1(\tau)$')
plt.plot(t, f_shift(t), label=r'$f_2(t_0-\tau)$')
plt.plot(t, prod(t), 'r-', label=r'$f_1(\tau)f_2(t_0-\tau)$')
# plot the convolution curve
plt.subplot(212)
plt.plot(t, convolution, label='$(f_1*f_2)(t)$')
# recalculate the value of the convolution integral at the current time-shift t0
current_value = scipy.integrate.simps(prod(t), t)
plt.plot(t0, current_value, 'ro') # plot the point
```

```
```

The animation shows, how the green function is gradually shifted to the right, producing more and more overlap between both curves and hence increasing the area under their product. Then, when the green curve is shifted even more to the right, we see that the area under their product decreases again.

Another important property can be seen from the convolution output: Even though the green input signal has a sharp jump at $t=0$, the convolution of both functions is a smooth function. In particular, this property is due to the integral in the convolution calculation: The integral somehow creates a moving average filter, which cannot create immediate jumps in the output signal (as long as the input does not contain Dirac-impulses). So, a general property of the convolution, is the fact that the convolution product of two functions is always a smoother curve than the input signals.

Let us now look at the classical example for convolution explanation: The convolution of a rectangular function with itself. Here, the result is a triangle with a maximum, when both rectangles perfectly overlap:

```
f1 = lambda t: (abs(t)<0.5).astype(float)
f2 = lambda t: 0.8*(abs(t-0.2)<0.5).astype(float)
plt.figure(figsize=(8,3))
plt.plot(t, f1(t), label='$f_1(t)$')
plt.plot(t, f2(t), label='$f_2(t)$')
```

```
```

```
f1 = lambda t: (t>0) * np.exp(-2*t)
f2 = lambda t: np.sin(2*np.pi*t) * (t>0)
plt.figure(figsize=(8,3))
plt.plot(t, f1(t), label='$f_1(t)$')
plt.plot(t, f2(t), label='$f_2(t)$')
```

```
```

- The convolution operation is given by the integral over the product of two functions, where one function is flipped and shifted in time.
- The convolution operation smoothes the input signals, i.e. the output of the convolution is a more smooth function that its input functions.
- Harmonic functions (i.e. sine, cosine) are the eigenfunctions of the convolution operation. This means convolving a sine with some function yields in a sine of different amplitude and phase, but same frequency.

Do you have questions or comments? Let's dicuss below!