3.4. Convolution modes#

As we saw in the first section of this chapter, the first few samples produced by a convolution \(y = x*h\) depend on negative sample indices \(x[n-k]\) where \(n < k\). To ensure that the convolution is well-defined for these indices, we assume \(x[n-k]=0\) for \(n<k < K\) (the length of the filter), which we may also think of as padding the signal with zeros on the left. This means that we can think of the first \(k\) samples of \(y\) as depending on these additional zeros. Any sample \(n \geq k\) will not depend on this padding.

Depending on how you want to use convolution, it may or may not be appropriate to include these first few samples. In this section, we’ll describe different commonly used modes of convolution, which can all be implemented by some combination of padding and trimming the inputs and outputs. Note that these modes do not fundamentally alter how convolution works: all that changes is which outputs are retained in the output signal.

Throughout this section, we will assume that \(x\) and \(h\) have \(N\) and \(K\) samples, respectively, and that \(N \geq K\). This latter assumption is for convenience, and as we’ll see in the next section, it is not necessary.

3.4.1. Full mode#

Full mode convolution is implemented by padding the input on both the left and the right by \(K-1\) samples each. A full mode convolution will have \(N + K - 1\) output samples.

The intuition for this is that it captures both a warm up phase (\(n < K\)), as well as a ringing out phase (\(N \leq n < N + K\)). The warm up phase contains all outputs which depend on some (but not only) prepended silence, the ringing out phase similarly contains outputs which depend on some (but not only) appended silence.

These effects can be seen in the example below, where we compute a full convolution between an increasing sequence \([5, 6, 7, 8, 9]\) and a differencing filter \([1, 0, -1]\). With this example, we’ll have \(y[n] = x[n] - x[n-2]\) in general, which will be the constant 2 for this sequence. However, at the beginning and end, you can see the effects of left- and right-padding by zeros.

# Full mode example
x = np.arange(5, 10)

# This is a differencing filter:
#   y[n] = x[n] - x[n-2]
h = np.array([1, 0, -1])

y = np.convolve(x, h, mode='full')

print('Input           :\t', x)
print('Filter          :\t', h)
print('Full convolution:\t', y)
Input           :	 [5 6 7 8 9]
Filter          :	 [ 1  0 -1]
Full convolution:	 [ 5  6  2  2  2 -8 -9]

3.4.2. Valid mode#

Valid mode convolution eliminates any outputs that depend on padding at all. If we start from the full mode convolution described above, the valid outputs are the middle 3, because they depend only on values explicitly observed in the input: \(7-5\), \(8-6\), and \(9-7\).

A valid mode convolution will have \(N - K + 1\) output samples.

y = np.convolve(x, h, mode='valid')
print('Input            :\t', x)
print('Filter           :\t', h)
print('Valid convolution:\t', y)
Input            :	 [5 6 7 8 9]
Filter           :	 [ 1  0 -1]
Valid convolution:	 [2 2 2]

3.4.3. Same mode#

Same mode is derived from full mode by extracting \(N\) samples from the full convolution, so that the output \(y\) has the same length \(N\) as \(x\). Most implementations of same mode extract the middle of the output, so that both the first and last \(K/2\) samples depend on zero padding, as demonstrated below:

y = np.convolve(x, h, mode='same')

print('Input           :\t', x)
print('Filter          :\t', h)
print('Same convolution:\t', y)
Input           :	 [5 6 7 8 9]
Filter          :	 [ 1  0 -1]
Same convolution:	 [ 6  2  2  2 -8]

Sometimes, you may want a left-aligned (or right-aligned) same-mode convolution, including the warm-up but not ring-out (or vice versa). These can be helpful if you want to introduce a delay effect to a signal, but retain its length. While the standard implementation used above does not do this, it’s easy enough to construct by trimming the full mode convolution differently:

# Include only the warm-up phase
N = len(x)
y = np.convolve(x, h, mode='full')
y = y[:N]  # Truncate to the first N samples

print('Input            :\t', x)
print('Filter           :\t', h)
print('Same (left-align):\t', y)
Input            :	 [5 6 7 8 9]
Filter           :	 [ 1  0 -1]
Same (left-align):	 [5 6 2 2 2]
# Discard the warm-up, but include the ring-out
N = len(x)
y = np.convolve(x, h, mode='full')
y = y[-N:]  # Truncate to the last N samples

print('Input             :\t', x)
print('Filter            :\t', h)
print('Same (right-align):\t', y)
Input             :	 [5 6 7 8 9]
Filter            :	 [ 1  0 -1]
Same (right-align):	 [ 2  2  2 -8 -9]

3.4.4. Summary#

The different convolution modes are summarized visually below, with \(h\) as the differencing filter used above, and \(x\) now as a square wave over 25 samples.

The shaded regions of the bottom subplot correspond to those which depend on zero-padding.

A figure illustrating the relationships between different convolution modes

Fig. 3.7 Top-left: an input signal \(x[n]\), followed by zero-padding. Top-right: the coefficients of a filter \(h[k]\). Remaining left plots: The full convolution \(y = h * x\) (dots) can be trimmed in various ways to produce each of the different convolution modes. The shaded regions correspond to output sample positions in the warm-up and ringing-out phases.#

The table below provides a rubric for deriving the different convolution modes from full mode in Python. For each mode, the length of the output signal is listed, as well as the slice notation for finding the different mode outputs within the full convolution.

In this table, we’ve assumed that \(N \geq K \geq 1\).

Mode

Length

Slice

Code

Full

\({N+K-1}\)

0 : N + K - 1

np.convolve(x, h, mode='full')

Valid

\({N-K+1}\)

K : N

np.convolve(x, h, mode='valid')

Same (left)

\(N\)

0 : N

np.convolve(x, h, mode='full')[:N]

Same (centered)

\(N\)

(K-1)//2 : N + (K-1)//2

np.convolve(x, h, mode='same')

Same (right)

\(N\)

K -1 : N + K-1

np.convolve(x, h, mode='full')[K-1:]

Remember that array slices in Python do not include the upper limit. For example, x[K:N] denotes indices x[K], x[K+1], x[K+2], x[N-1].