# How to do RRC filtering in multiple stages?

The goal of the approach is to speed up RRC filtering with (very) large oversampling ratio. To this end I calculate the prime factors of the oversampling ratio and RRC filter the signal with each factor feeding the previous result to the next. To test this approach I compare it, using the same base function rrcosfilter (not the one from commpy), to the case where the filtering is done in one go. The spectrum agrees quite well in-band, but out-of-band I get some aliasing effect with the factorized approach. How can I avoid these in-band copies and still use this factorized approach?

The main script:

from <custom lib> import *
import numpy as np
from matplotlib import pyplot as plt

def fft(x, sample_rate):
Y_dB = 20*np.log10(np.abs(np.fft.fftshift(np.fft.fft(x))))
f = np.fft.fftshift(np.fft.fftfreq(len(Y_dB), 1/sample_rate))
return f, Y_dB

sample_rate = 10
symbol_rate = 1

pam = PAM(1.0, 1000, symbol_rate, 16)

syms = pam.get_symbols()
# own redefined function
x = rrcosfilter(syms, sample_rate//symbol_rate, 0.3, 10)

y = pam.get_rrc_filtered(sample_rate)

fx, X_dB = fft(x, sample_rate)
fy, Y_dB = fft(y, sample_rate)

fig, ax = plt.subplots()

ax.plot(fx, X_dB, label='direct')
ax.plot(fy, Y_dB, label='factorized')
ax.grid()
ax.margins(x=0)
ax.set_xlabel('Frequency (Hz)')
ax.set_ylabel('Power (dB)')

plt.show()


The functions:

import numpy as np
from numpy.typing import NDArray
from typing import Optional, List
import commpy.filters as cmpfilters

def get_prime_factors(n: int) -> List[int]:
"""
Gets the prime factors of the given number.

:param n: the number to factorize
:type n: int
:return: a list of factors
:rtype: list[int]
"""
i = 2
factors = []

while i**2 <= n:
if n % i:
i += 1
else:
n //= i
factors.append(i)
if n > 1:
factors.append(n)

return factors

def rrcosfilter(x: NDArray, samples_per_symbol: int, alpha: float, N: int) -> NDArray:
"""

"""

# the filter length in number of samples, i.e. after oversampling of the symbols
filter_length = 2*N*samples_per_symbol + 1
psf = cmpfilters.rrcosfilter(filter_length, alpha, 1, samples_per_symbol)[1]

# remove filter tails
return y[N*samples_per_symbol:-N*samples_per_symbol]

def rrcosfilter_factorized(x: NDArray, Rsym: int, Rs: int, alpha: float, N: int) -> NDArray:
"""
Applies a root-raised cosine filter to the given signal. This implementation factorizes the oversampling ratio and does the filtering in multiple steps to speed up the process.

:param x: symbols
:type x: NDArray[float]
:param Rsym: symbol rate (Baud)
:type Rsym: int
:param Rs: filtered sample rate (sps)
:type Rs: int
:param alpha: roll-off factor
:type alpha: float
:param N: one-sided filter length in number of symbols
:type N: int
:return: filtered symbol sequence
:rtype: NDArray[float]
"""
# the oversampling rate
samples_per_symbol = Rs//Rsym

# check if the upsampling is fractional
if abs(samples_per_symbol - Rs/Rsym) > 0:
logger.error("Fractional oversampling is not implemented. You tried an upsampling factor of %s.", Rs/Rsym)
return None

# pad beginning and ending of data with symbols to make it cyclic
x_cylcic = np.concatenate((x[-N:], x, x[:N]))

# decompose the oversampling rate into its prime factors
factors = get_prime_factors(samples_per_symbol)

# apply the RRC filter for every factor and pass the filtered signal to the next iteration
y = x_cylcic
for factor in factors:
y = rrcosfilter(y, factor, alpha, N)

# remove cyclic pre- and post-symbols
return y[N*samples_per_symbol:-N*samples_per_symbol]

• I'm missing where you actually factorize the filter; can you point me to the function that calculates the taps of the factor filter(s)? Commented May 17 at 14:07
• The filter is not factorized, but the oversampling ratio. I might have worded this not well enough. My apologies for this. In rrcosfilter_factorized the prime factors of the oversampling ratio are found through get_prime_factors. For each factor the rrcosfilter function is called which performs the normal root-raised cosine filtering using the given oversampling ratio (factor). Commented May 17 at 14:18
• then what you see would seem pretty reasonable. What you've built is not equivalent to your single step RRC, by design (unless I am missing something very fundamental about the RRC, but I don't think I do – your spectrum plot confirms that.) Commented May 17 at 14:28
• oh and you're using far too few symbols (if 1000 is the number of symbols) to estimate a spectrum to a sufficient degree. You should, instead of testing your filter with pseudo-white data, simply do an explicit analysis – your filter is deterministic, after all – with freqz Commented May 17 at 14:30
• Okay, I assumed filtering the signal over and over with a lower oversampling ratio would have the same effect as filtering it once with the total oversampling ratio or at least not lead to this aliasing effect. Commented May 17 at 14:41