The way it is drawn suggests that $\hat{c}[k]$ samples are the complex decisions for the demodulated symbols. This assumes correct timing to only use the samples for demodulation. Instead, assuming a training sequence is used, I recommend comparing all samples to the “ideal” waveform given by the training sequence at the same sampling rate as it would appear in this location of the receiver (as then a fractionally spaced equalizer). The switch between training and tracking would then have to change from a fractional spaced equalizer to a baud rate equalizer if implemented as shown.
As to MATLABBelow is Python code, please I have for the implementation of a normalized LMS equalizer:
def LMS(input_signal, desired_signal, num_taps, alpha, shift=0):
'''
LMS(input_signal, desired_signal, num_taps, alpha, shift=0)
LMS equalizer implementation Dan Boschen 10-12-2023
input parameters:
input_signal: received signal (1D array like)
desired_signal: transmitted (reference) signal (1D array like)
num_taps: length of equalizer (positive integer)
alpha: damping coefficient, typically < 0.01 (positive float)
shift: number of samples to shift dominant equalizer tap to the right (signed integer)
input_signal and desired_signal should be aligned in time with zero phase
returns:
y: equalized output (1D array)
coeff: converged channel coefficients (1D array)
e: error vs iteration (1D array)
'''
if shift > 0:
rx_shift = shift
tx_shift = 0
else:
tx_shift = -shift
rx_shift = 0
# align to delay of equalizer
input_signal = input_signal[rx_shift + (num_taps-1)//2:]
desired_signal = desired_signal[tx_shift:]
num_points = min(len(input_signal), len(desired_signal))
# normalize signals
input_signal = input_signal[:num_points]/np.std(input_signal)
desired_signal = desired_signal[:num_points]/np.std(desired_signal)
coeff = np.zeros(num_taps, dtype=complex)
y = np.zeros(num_points, dtype=complex)
e = np.zeros(num_points, dtype=complex)
for n in np.arange(num_taps, num_points):
x = input_signal[n:n-num_taps:-1]
y[n] = np.dot(np.conj(coeff), x) # predicted output
e[n] = y[n] - desired_signal[n] # error
coeff = coeff - alpha * x * np.conj(e[n])
return y, np.conj(coeff), e
Please also see this post hereDSP.SE #31318 which details the process to find the least squared solution for the coefficients when post processing iscan be used. Working code is typically out of scope for this site, but forFor adaptive solutions seein addition to the simpler LMS algorithm detailed above (very easy but slow to converge) oralso consider the Recursive Least Squares (more complex, fast convergence and good tracking).