plenty of new code and stuff - mostly AI slop and Dissertation. I will move to my own git now
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
classdef EQ_silas_sliding_window_dc_removal < handle
|
||||
%EQ_SILAS FFE and DFE Equalizer Playground
|
||||
|
||||
properties
|
||||
% Important Signals
|
||||
x_in %Input Sequence to be equalized
|
||||
x_length
|
||||
x_norm
|
||||
|
||||
d %reference signal
|
||||
d_norm
|
||||
d_constellation %constellation points of the reference
|
||||
|
||||
y_out %equalizer output signal
|
||||
|
||||
% FFE coefficients always named with "e"
|
||||
Ne
|
||||
Ce %memory length FFE
|
||||
Ie1 %Indice Combination of 1nd order FFE
|
||||
Ie2 %Indice Combination of 2nd order FFE
|
||||
Ie3 %Indice Combination of 3nd order FFE
|
||||
e %coefficients for FFE
|
||||
|
||||
% DFE coefficients always named with "b"
|
||||
Nb
|
||||
Cb %memory length DFE
|
||||
Ib1 %Indice Combination of 1nd order DFE
|
||||
Ib2 %Indice Combination of 2nd order DFE
|
||||
Ib3 %Indice Combination of 3nd order DFE
|
||||
b %coefficients for DFE
|
||||
|
||||
error
|
||||
e_ffe
|
||||
e_dfe
|
||||
e_dc
|
||||
|
||||
% coefficients
|
||||
mu_dc_train
|
||||
mu_ffe_train
|
||||
mu_dfe_train
|
||||
|
||||
mu_dc_dd
|
||||
mu_ffe_dd
|
||||
mu_dfe_dd
|
||||
mu_combined_dd % [1st order FFE, 2nd order FFE, 3rd order FFE, all orders DFE]
|
||||
|
||||
delay
|
||||
trainlength
|
||||
sps
|
||||
|
||||
trainloops
|
||||
ddloops
|
||||
|
||||
eq_blocklength % block lengt of EQ (until now, only the dc subtraction is affected by this)
|
||||
eq_updatelatency % time in symbols until the calculated updates reach the signal again (until now, only the dc subtraction is affected by this)
|
||||
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = EQ_silas_sliding_window_dc_removal(options)
|
||||
%EQ_SILAS Construct an instance of this class
|
||||
arguments(Input)
|
||||
options.Ne = [50 5 0] %Number of FFE coefficients (1st, 2nd and 3rd order)
|
||||
options.Nb = [30 5 3] %Number of DFE coefficients (1st, 2nd and 3rd order)
|
||||
options.trainloops = 2;
|
||||
options.trainlength = 4096;
|
||||
options.ddloops = 2;
|
||||
|
||||
options.delay = 0;
|
||||
options.sps = 2;
|
||||
|
||||
options.mu_dc_train = 0.01;
|
||||
options.mu_ffe_train = 0.005;
|
||||
options.mu_dfe_train = 0.005;
|
||||
|
||||
options.mu_dc_dd = 0.01;
|
||||
options.mu_ffe_dd = [0.0004 0.0005 0.0006];
|
||||
options.mu_dfe_dd = 0.0005;
|
||||
|
||||
options.eq_blocklength = 1;
|
||||
options.eq_updatelatency = 1;
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
% Generate helpful vectors and initialize the filters with
|
||||
% correct length:
|
||||
|
||||
obj.Ce = obj.calcVNLEMemoryLength(obj.Ne);
|
||||
|
||||
[obj.Ie2,obj.Ie3] = obj.calcIndiceVectors(obj.Ne);
|
||||
|
||||
obj.e = zeros(sum(obj.Ce),1);
|
||||
|
||||
|
||||
obj.Cb = obj.calcVNLEMemoryLength(obj.Nb);
|
||||
|
||||
[obj.Ib2,obj.Ib3] = obj.calcIndiceVectors(obj.Nb);
|
||||
|
||||
obj.b = zeros(sum(obj.Cb),1);
|
||||
|
||||
end
|
||||
|
||||
function [signalclass_out] = process(obj,signalclass_in, reference_signalclass_in)
|
||||
|
||||
% actual processing of the signal (steps 1. - 3.)
|
||||
% 1 normalize RMS
|
||||
signalclass_in = signalclass_in.normalize("mode","rms");
|
||||
|
||||
% Process the EQ optimization
|
||||
obj.process_(signalclass_in.signal', reference_signalclass_in.signal');
|
||||
|
||||
signalclass_in.signal = obj.y_out';
|
||||
% append to logbook
|
||||
lbdesc = ['EQ von Silas ist gelaufen '];
|
||||
signalclass_in = signalclass_in.logbookentry(lbdesc);
|
||||
|
||||
% write to output
|
||||
signalclass_out = signalclass_in;
|
||||
|
||||
end
|
||||
|
||||
function process_(obj,x_in,d_in)
|
||||
|
||||
% 1) prepare signals
|
||||
obj.e_dc = mean(x_in);
|
||||
|
||||
% 1.1) Input Signal
|
||||
obj.x_in = [zeros(1,floor(obj.Ne(1)/2)) x_in zeros(1,obj.Ne(1))];
|
||||
obj.x_length = length(x_in);
|
||||
obj.x_norm = obj.calcPowerNormalization(x_in);
|
||||
|
||||
% 1.2 Reference Signal // Constellation
|
||||
obj.d = [zeros(1,obj.Nb(1)-1) d_in zeros(1,obj.Nb(1))];
|
||||
obj.d_constellation = unique(d_in);
|
||||
obj.d_norm = obj.calcPowerNormalization(d_in);
|
||||
|
||||
% 1.3 Training
|
||||
obj.trainingMode();
|
||||
|
||||
% 1.4 Decision Directed Mode
|
||||
obj.decisionDirectedMode();
|
||||
|
||||
end
|
||||
|
||||
%% Adaptive Equalization Modes
|
||||
|
||||
function trainingMode(obj)
|
||||
|
||||
dc_avg_block = zeros(obj.eq_blocklength,1);
|
||||
|
||||
for tloop = 1:obj.trainloops
|
||||
m = 1+obj.delay;
|
||||
|
||||
for n = obj.sps*obj.delay+1:obj.sps:obj.sps*obj.trainlength
|
||||
m = m+1;
|
||||
|
||||
%get Sigal input vectors with correct length for VNLE
|
||||
x_in_block = obj.x_in(obj.Ne(1)+n+(obj.sps-1):-1:n+obj.sps).';
|
||||
|
||||
x_in_vnle_format = obj.calcVNLENonlinVecs(x_in_block,obj.Ie2,obj.Ie3,obj.Ne,obj.x_norm);
|
||||
|
||||
%get Reference input vectors with correct length for VNLE
|
||||
d_block = obj.d(obj.Nb(1)-obj.delay+m-2:-1:m-obj.delay-1).';
|
||||
d_vnle_format = obj.calcVNLENonlinVecs(d_block,obj.Ib2,obj.Ib3,obj.Nb,obj.d_norm);
|
||||
|
||||
obj.e_ffe = obj.e.' * x_in_vnle_format;
|
||||
|
||||
dc_avg_block = circshift(dc_avg_block,1);
|
||||
|
||||
dc_avg_block(1) = obj.e_ffe;
|
||||
|
||||
if n > obj.eq_blocklength * obj.sps
|
||||
obj.e_dc = obj.e_ffe - mean(dc_avg_block).* obj.mu_dc_train;
|
||||
else
|
||||
obj.e_dc = 0;
|
||||
end
|
||||
|
||||
obj.e_dfe = obj.b.' * d_vnle_format;
|
||||
|
||||
% Calculate the Error
|
||||
obj.error = obj.e_ffe - obj.e_dfe - obj.d(obj.Nb(1)-1+m-obj.delay);
|
||||
|
||||
if obj.mu_ffe_train ~= 0
|
||||
%update FFE coefficients with LMS
|
||||
obj.e = obj.e - obj.error*conj(x_in_vnle_format)*obj.mu_ffe_train;
|
||||
else
|
||||
%update FFE coefficients with NLMS
|
||||
obj.e = obj.e - obj.error*x_in_vnle_format/(x_in_vnle_format.'*x_in_vnle_format);
|
||||
end
|
||||
|
||||
%update DFE coefficients with LMS
|
||||
obj.b = obj.b + obj.mu_dfe_train*obj.error*d_vnle_format;
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
function decisionDirectedMode(obj)
|
||||
|
||||
%start the dd mode with coefficients from training
|
||||
coeff = [obj.e;obj.b];
|
||||
|
||||
|
||||
for ddloop = 1:obj.ddloops
|
||||
|
||||
dc_avg_block = zeros(obj.eq_blocklength,1);
|
||||
m = 0;
|
||||
dc_cnt = 0;
|
||||
|
||||
mu_mat = diag([ones(1,obj.Ce(1))*obj.mu_ffe_dd(1)... %1st order ffe
|
||||
ones(1,obj.Ce(2))*obj.mu_ffe_dd(2)... %2nd order ffe
|
||||
ones(1,obj.Ce(3))*obj.mu_ffe_dd(3)... %3rd order ffe
|
||||
ones(1,sum(obj.Cb))*obj.mu_dfe_dd]); %all order dfe
|
||||
|
||||
mu_ffe = [ones(1,obj.Ce(1))*obj.mu_ffe_dd(1)... %1st order ffe
|
||||
ones(1,obj.Ce(2))*obj.mu_ffe_dd(2)... %2nd order ffe
|
||||
ones(1,obj.Ce(3))*obj.mu_ffe_dd(3)];
|
||||
|
||||
mu_dfe = ones(1,sum(obj.Cb))*obj.mu_dfe_dd;
|
||||
|
||||
y = zeros(1,floor(obj.x_length/obj.sps));
|
||||
y_= zeros(1,floor(obj.x_length/obj.sps));
|
||||
d_feedback = zeros(obj.Cb(1),1);
|
||||
d_vnle = obj.calcVNLENonlinVecs(d_feedback,obj.Ib2,obj.Ib3,obj.Nb,obj.d_norm);
|
||||
d_hat = zeros(obj.x_length,1);
|
||||
|
||||
for k = 1:obj.sps:obj.x_length
|
||||
dc_cnt = dc_cnt+1;
|
||||
m=m+1;
|
||||
|
||||
%get Sigal input vectors with correct length for VNLE
|
||||
x = obj.x_in(obj.Ne(1)+k-1:-1:k).';
|
||||
|
||||
x_vnle = obj.calcVNLENonlinVecs(x,obj.Ie2,obj.Ie3,obj.Ne,obj.x_norm);
|
||||
|
||||
%combine FFE with DFE to one vector (cursor between the two sequences)
|
||||
x_d = [x_vnle;-d_vnle];
|
||||
|
||||
%Apply filter
|
||||
y_(m) = x_d.'* coeff ;
|
||||
y(m) = y_(m) - obj.e_dc(end) ;
|
||||
|
||||
dc_avg_block = circshift(dc_avg_block,1);
|
||||
|
||||
dc_avg_block(1) = y(m);
|
||||
|
||||
obj.e_dc(m) = (y(m) - mean(dc_avg_block)) .* obj.mu_dc_dd;
|
||||
|
||||
%Decision
|
||||
[~,symbol_idx] = min(abs(y(m) - obj.d_constellation)); % decision for closest constellation point
|
||||
|
||||
d_hat(k) = obj.d_constellation(symbol_idx);
|
||||
|
||||
%Error between FFE & DFE filtered signal and Decision
|
||||
obj.error = y(m) - d_hat(k);
|
||||
|
||||
%Update coefficients (both FFE and DFE)
|
||||
% obj.e = obj.e - obj.error * mu_ffe * conj(x_vnle);
|
||||
% obj.b = obj.b + obj.error * mu_dfe * conj(d_vnle);
|
||||
% coeff = [obj.e;obj.b];
|
||||
|
||||
coeff = coeff - mu_mat*obj.error*conj(x_d);
|
||||
|
||||
% Append new decision to decision feedback
|
||||
if obj.Nb(1) > 0
|
||||
|
||||
%shift up one index
|
||||
d_feedback(2:end) = d_feedback(1:end-1);
|
||||
%replace 1st index with current estimation
|
||||
d_feedback(1) = d_hat(k);
|
||||
%build memorylike VNLE version
|
||||
d_vnle = obj.calcVNLENonlinVecs(d_feedback,obj.Ib2,obj.Ib3,obj.Nb,obj.d_norm);
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
obj.y_out = (circshift( y.' ,-(obj.delay))).';
|
||||
|
||||
end
|
||||
|
||||
%% Functions needed During Adaption
|
||||
function x_in_vnle_format = calcVNLENonlinVecs(~,x_in_block,I_2,I_3,N_,norm_)
|
||||
% These are the second and third order input signal products of the VNLE EQ
|
||||
% ∑ h1 x_in(k-n1) + ∑∑ h2 x_in(k-n1)*x_in(k-n2) + ∑∑∑ h3 x_in(k-n1)*x_in(k-n2)*x_in(k-n3)
|
||||
l1=length(x_in_block);
|
||||
l2=length(I_2);
|
||||
l3=length(I_3);
|
||||
final_length = l1+l2+l3;
|
||||
|
||||
x_in_vnle_format = zeros(final_length,1);
|
||||
|
||||
idx = l1;
|
||||
x_in_vnle_format(1:idx) = x_in_block;
|
||||
|
||||
if N_(2) > 0
|
||||
delta_2 = round((N_(1)-N_(2)) / 2);
|
||||
input_vec_se = x_in_block(delta_2:end) / norm_(2); %TODO normalization step
|
||||
|
||||
% Extract columns from I_2
|
||||
col1 = input_vec_se(I_2(:,1));
|
||||
col2 = input_vec_se(I_2(:,2));
|
||||
|
||||
x2 = col1 .* col2;
|
||||
x_in_vnle_format(idx+1:idx+l2) = x2;
|
||||
end
|
||||
|
||||
if N_(3) > 0
|
||||
delta_3 = round((N_(1)-N_(3))/2);
|
||||
input_vec_th = x_in_block(delta_3:end) / norm_(3);
|
||||
|
||||
% Extract columns from I_3
|
||||
col1 = input_vec_th(I_3(:,1));
|
||||
col2 = input_vec_th(I_3(:,2));
|
||||
col3 = input_vec_th(I_3(:,3));
|
||||
|
||||
% Perform matrix multiplication
|
||||
x3 = col1 .* col2 .* col3;
|
||||
|
||||
idx = idx+l2;
|
||||
x_in_vnle_format(idx+1:idx+l3) = x3;
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
%% Functions needed for Preparation
|
||||
function [C] = calcVNLEMemoryLength(~,N)
|
||||
|
||||
%calculates the memory length of VNLE
|
||||
C = zeros(size(N));
|
||||
|
||||
for o = 1:numel(N)
|
||||
switch o
|
||||
case 1
|
||||
C(o) = N(o);
|
||||
case 2
|
||||
C(o) = N(o)*(N(o)+1) / 2;
|
||||
case 3
|
||||
C(o) = N(o)*(N(o)+1)*(N(o)+2) / 6;
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function [indvec2nd, indvec3rd] = calcIndiceVectors(~,N)
|
||||
|
||||
% Init vectors of 2nd and 3rd order coefficient indices ->
|
||||
% yield combination with
|
||||
|
||||
for order = 2:numel(N)
|
||||
n = N(order);
|
||||
v = 1:n; % Ursprünglicher Vektor
|
||||
row = 1;
|
||||
|
||||
% Schleifen zur Generierung des Indize Vektors
|
||||
switch order
|
||||
|
||||
case 2
|
||||
|
||||
indvec2nd = zeros(n*(n+1)/2, order);
|
||||
for i = 1:n
|
||||
for j = i:n
|
||||
indvec2nd(row, :) = [v(i) v(j)];
|
||||
row = row + 1;
|
||||
end
|
||||
end
|
||||
|
||||
case 3
|
||||
|
||||
indvec3rd = zeros(n*(n+1)*(n+2)/6, 3);
|
||||
for i = 1:n
|
||||
for j = i:n
|
||||
for k = j:n
|
||||
indvec3rd(row, :) = [v(i) v(j) v(k)];
|
||||
row = row + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function powerNorm = calcPowerNormalization(~,v)
|
||||
|
||||
powerNorm(1) = sqrt(mean(abs(v ).^2));
|
||||
powerNorm(2) = sqrt(mean(abs(v.^2).^2));
|
||||
powerNorm(3) = sqrt(mean(abs(v.^3).^2));
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
171
Classes/04_DSP/Equalizer/MPI_reduction/FFE_DCremoval.m
Normal file
171
Classes/04_DSP/Equalizer/MPI_reduction/FFE_DCremoval.m
Normal file
@@ -0,0 +1,171 @@
|
||||
classdef FFE_DCremoval < handle
|
||||
% Implementation of plain and simple FFE.
|
||||
% 1) Training mode (stable performance when you use NLMS)
|
||||
% 2) Decision directed mode
|
||||
|
||||
% Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0);
|
||||
|
||||
properties
|
||||
sps % usually 2
|
||||
order
|
||||
e
|
||||
error
|
||||
|
||||
len_tr
|
||||
mu_tr
|
||||
epochs_tr
|
||||
|
||||
mu_dd
|
||||
epochs_dd
|
||||
|
||||
mu_dc
|
||||
dc_buffer_len
|
||||
|
||||
constellation
|
||||
|
||||
decide
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = FFE_DCremoval(options)
|
||||
arguments(Input)
|
||||
|
||||
options.sps = 2;
|
||||
options.order = 15;
|
||||
|
||||
options.len_tr = 4096;
|
||||
options.mu_tr = 0;
|
||||
options.epochs_tr = 5;
|
||||
|
||||
options.mu_dd = 1e-5;
|
||||
options.epochs_dd = 5;
|
||||
|
||||
options.mu_dc = 0.05;
|
||||
options.dc_buffer_len = 1;
|
||||
|
||||
options.decide = false;
|
||||
|
||||
end
|
||||
|
||||
assert(options.dc_buffer_len>0);
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
obj.e = zeros(obj.order,1);
|
||||
obj.error = 0;
|
||||
|
||||
obj.dc_buffer_len = floor(obj.dc_buffer_len);
|
||||
|
||||
end
|
||||
|
||||
function [X,Noi] = process(obj, X, D)
|
||||
|
||||
% actual processing of the signal (steps 1. - 3.)
|
||||
% 1 normalize RMS
|
||||
X = X.normalize("mode","rms");
|
||||
|
||||
obj.constellation = unique(D.signal);
|
||||
|
||||
% Training Mode
|
||||
training = 1;
|
||||
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training);
|
||||
|
||||
% Decision Directed Mode
|
||||
N = X.length;
|
||||
training = 0;
|
||||
[signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,N,training);
|
||||
|
||||
% Output Signal
|
||||
if obj.decide
|
||||
X.signal = decision;
|
||||
else
|
||||
X.signal = signal;
|
||||
end
|
||||
X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
|
||||
lbdesc = [num2str(obj.order),' tap FFE'];
|
||||
X = X.logbookentry(lbdesc); % append to logbook
|
||||
|
||||
Noi = X;
|
||||
Noi = X - D;
|
||||
|
||||
end
|
||||
|
||||
function [y,d_hat] = equalize(obj,x,d,mio,epochs,N,training)
|
||||
|
||||
arguments
|
||||
obj
|
||||
x
|
||||
d
|
||||
mio
|
||||
epochs
|
||||
N
|
||||
training
|
||||
end
|
||||
|
||||
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
|
||||
|
||||
err = 0;
|
||||
e_dc_buffer = NaN(obj.dc_buffer_len,1);
|
||||
e_dc_est = 0;
|
||||
|
||||
for epoch = 1 : epochs
|
||||
symbol = 0;
|
||||
for sample = 1 : obj.sps : N
|
||||
|
||||
symbol = symbol+1;
|
||||
|
||||
U = x(obj.order+sample-1:-1:sample);
|
||||
|
||||
y(symbol,1) = e_dc_est + obj.e.' * U; % Calculating output of LMS __ * |
|
||||
|
||||
if training
|
||||
d_hat(symbol,1) = d(symbol);
|
||||
else
|
||||
[~,symbol_idx] = min(abs(y(symbol) - obj.constellation)); % decision for closest constellation point
|
||||
d_hat(symbol,1) = obj.constellation(symbol_idx);
|
||||
end
|
||||
|
||||
err(symbol) = y(symbol) - d_hat(symbol); % Instantaneous error
|
||||
|
||||
if mio ~= 0
|
||||
obj.e = obj.e - (mio * err(symbol) * U) ; % Weight update rule of LMS
|
||||
else
|
||||
normalizationfactor = (U.' * U);
|
||||
obj.e = obj.e - err(symbol) * U / normalizationfactor; % Weight update rule of NLMS
|
||||
end
|
||||
|
||||
%Update the dc estimation every n-th symbol. This is a
|
||||
%trivial implementation of parallel EQ´s where the
|
||||
%errors are not apparent in every step. See Silas OFC
|
||||
%2023 "MPI mitigation adaptive DC removal
|
||||
if mod(symbol,length(e_dc_buffer)) == 0
|
||||
e_dc_buffer(1) = e_dc_est - obj.mu_dc * err(symbol);
|
||||
e_dc_buffer = circshift(e_dc_buffer,1);
|
||||
e_dc_est = mean(e_dc_buffer,"omitnan");
|
||||
else
|
||||
e_dc_buffer(1) = e_dc_est - obj.mu_dc * err(symbol);
|
||||
e_dc_buffer = circshift(e_dc_buffer,1);
|
||||
end
|
||||
|
||||
e_dc_save(symbol) = e_dc_est;
|
||||
|
||||
obj.error(epoch,symbol) = err(symbol) * err(symbol)'; % Instantaneous square error
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
% if ~training
|
||||
% figure(1122)
|
||||
% hold on
|
||||
% scatter(1:numel(e_dc_save),e_dc_save,1,'.');
|
||||
% % scatter(1:numel(y),y,1,'.');
|
||||
%
|
||||
% end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,411 @@
|
||||
classdef FFE_DCremoval_adaptive_mu < handle
|
||||
% FFE variant for MPI/DC-removal experiments.
|
||||
% With dc_buffer_len <= 1, ffe_buffer_len <= 1 and no smoothing, this
|
||||
% follows FFE.m semantics so MPI-reduction changes can be isolated.
|
||||
|
||||
properties
|
||||
sps
|
||||
order
|
||||
e
|
||||
e_tr
|
||||
error
|
||||
|
||||
len_tr
|
||||
mu_tr
|
||||
epochs_tr
|
||||
|
||||
adaption_technique
|
||||
dd_mode
|
||||
mu_dd
|
||||
epochs_dd
|
||||
dd_len_fraction
|
||||
mu_dc
|
||||
e_dc
|
||||
|
||||
P
|
||||
|
||||
dc_buffer_len
|
||||
adaptive_mu_mode
|
||||
ffe_buffer_len
|
||||
smoothing_buffer_length
|
||||
smoothing_buffer_update
|
||||
|
||||
constellation
|
||||
|
||||
decide
|
||||
|
||||
save_debug = 0;
|
||||
debug_struct
|
||||
|
||||
optmize_mus = 0;
|
||||
mu_optimization
|
||||
mu_optimization_iter = 0;
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = FFE_DCremoval_adaptive_mu(options)
|
||||
arguments(Input)
|
||||
options.sps = 2;
|
||||
options.order = 15;
|
||||
|
||||
options.len_tr = 4096;
|
||||
options.mu_tr = 0;
|
||||
options.epochs_tr = 5;
|
||||
|
||||
options.adaption_technique adaption_method = adaption_method.lms;
|
||||
options.dd_mode = 1;
|
||||
options.mu_dd = 1e-5;
|
||||
options.epochs_dd = 5;
|
||||
options.dd_len_fraction = 0.25;
|
||||
|
||||
options.mu_dc = 0.05;
|
||||
options.dc_buffer_len = 1;
|
||||
options.adaptive_mu_mode = 1;
|
||||
options.ffe_buffer_len = 1;
|
||||
options.smoothing_buffer_length = 0;
|
||||
options.smoothing_buffer_update = 0;
|
||||
|
||||
options.decide = false;
|
||||
|
||||
options.save_debug = 0;
|
||||
options.optmize_mus = 0;
|
||||
end
|
||||
|
||||
assert(options.dc_buffer_len >= 0);
|
||||
assert(options.ffe_buffer_len >= 0);
|
||||
assert(options.smoothing_buffer_length >= 0);
|
||||
if options.smoothing_buffer_length > 0
|
||||
assert(options.smoothing_buffer_update > 0);
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
obj.e = zeros(obj.order,1);
|
||||
obj.e_dc = 0;
|
||||
obj.error = 0;
|
||||
|
||||
obj.dc_buffer_len = floor(obj.dc_buffer_len);
|
||||
obj.ffe_buffer_len = floor(obj.ffe_buffer_len);
|
||||
obj.smoothing_buffer_length = floor(obj.smoothing_buffer_length);
|
||||
obj.smoothing_buffer_update = floor(obj.smoothing_buffer_update);
|
||||
end
|
||||
|
||||
function [X,Noi] = process(obj, X, D)
|
||||
X = X.normalize("mode","rms");
|
||||
|
||||
obj.constellation = unique(D.signal);
|
||||
obj.e_dc = 0;
|
||||
|
||||
delta = 0.05;
|
||||
obj.P = (1/delta) * eye(obj.order);
|
||||
|
||||
if obj.optmize_mus
|
||||
obj.optimizeMus(X.signal,D.signal);
|
||||
obj.e = zeros(obj.order,1);
|
||||
obj.e_dc = 0;
|
||||
obj.P = (1/delta) * eye(obj.order);
|
||||
end
|
||||
|
||||
training = 1;
|
||||
showviz = 0;
|
||||
obj.equalize(X.signal,D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
|
||||
obj.e_tr = obj.e;
|
||||
|
||||
n = X.length;
|
||||
training = 0;
|
||||
if obj.dd_mode
|
||||
n_dd = obj.ddLength(n);
|
||||
obj.equalize(X.signal,D.signal,obj.mu_dd,obj.epochs_dd,n_dd,training,showviz);
|
||||
end
|
||||
[signal,decision] = obj.applyCurrentTaps(X.signal,n);
|
||||
|
||||
if obj.decide
|
||||
X.signal = decision;
|
||||
else
|
||||
X.signal = signal;
|
||||
end
|
||||
|
||||
X.fs = D.fs;
|
||||
lbdesc = [num2str(obj.order),' tap FFE'];
|
||||
X = X.logbookentry(lbdesc);
|
||||
|
||||
Noi = X;
|
||||
Noi = X - D;
|
||||
end
|
||||
|
||||
function [y,d_hat] = equalize(obj,x,d,mu,epochs,N,training,showviz)
|
||||
arguments
|
||||
obj
|
||||
x
|
||||
d
|
||||
mu
|
||||
epochs
|
||||
N
|
||||
training
|
||||
showviz = 0 %#ok<INUSD>
|
||||
end
|
||||
|
||||
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
|
||||
lambda = mu;
|
||||
|
||||
if training
|
||||
mask = ones(obj.order,1);
|
||||
else
|
||||
mask = zeros(obj.order,1);
|
||||
mask(900:end) = 1;
|
||||
mask(ceil(length(obj.e)/2)) = 1;
|
||||
end
|
||||
|
||||
mask = ones(obj.order,1);
|
||||
always_ideal_decision = 0;
|
||||
grad = 0;
|
||||
weight = 0;
|
||||
update = 0;
|
||||
|
||||
if mu == 0 || (~obj.dd_mode && ~training)
|
||||
epochs = 1;
|
||||
end
|
||||
|
||||
dc_buffer_enabled = obj.mu_dc ~= 0 && obj.dc_buffer_len > 1;
|
||||
adaptive_dc_enabled = dc_buffer_enabled && obj.adaptive_mu_mode;
|
||||
if dc_buffer_enabled
|
||||
e_dc_buffer = NaN(obj.dc_buffer_len,1);
|
||||
end
|
||||
|
||||
ffe_buffer_enabled = ~training && obj.ffe_buffer_len > 1 && ...
|
||||
obj.adaption_technique ~= adaption_method.rls;
|
||||
if ffe_buffer_enabled
|
||||
grad_buffer = NaN(obj.order,obj.ffe_buffer_len);
|
||||
end
|
||||
|
||||
if obj.smoothing_buffer_length > 0
|
||||
smoothing_buffer = ones(1,obj.smoothing_buffer_length) .* mean(obj.constellation);
|
||||
smoothing_mean = mean(obj.constellation);
|
||||
end
|
||||
|
||||
P_err = 0;
|
||||
alpha = 0.98;
|
||||
err_prev = 0;
|
||||
gamma_dc = 1e-6;
|
||||
mu_min = 1e-6;
|
||||
mu_max = 3e-1;
|
||||
|
||||
for epoch = 1 : epochs
|
||||
symbol = 0;
|
||||
for sample = 1 : obj.sps : N
|
||||
symbol = symbol + 1;
|
||||
|
||||
if obj.smoothing_buffer_length > 0
|
||||
smoothing_buffer = circshift(smoothing_buffer,1,2);
|
||||
smoothing_buffer(1) = x(sample);
|
||||
if mod(symbol,obj.smoothing_buffer_update) == 0
|
||||
smoothing_mean = mean(smoothing_buffer);
|
||||
end
|
||||
x(sample:sample+obj.sps-1) = x(sample:sample+obj.sps-1) - smoothing_mean;
|
||||
end
|
||||
|
||||
U = x(obj.order+sample-1:-1:sample);
|
||||
|
||||
y(symbol,1) = obj.e_dc + (obj.e.*mask).' * U;
|
||||
|
||||
if training
|
||||
d_hat(symbol,1) = d(symbol);
|
||||
else
|
||||
if ~always_ideal_decision
|
||||
[~,symbol_idx] = min(abs(y(symbol) - obj.constellation));
|
||||
d_hat(symbol,1) = obj.constellation(symbol_idx);
|
||||
else
|
||||
d_hat(symbol,1) = d(symbol);
|
||||
end
|
||||
end
|
||||
|
||||
err(symbol) = d_hat(symbol) - y(symbol); %#ok<AGROW>
|
||||
true_err(symbol) = y(symbol) - d(symbol); %#ok<AGROW,NASGU>
|
||||
|
||||
if training || obj.dd_mode
|
||||
switch obj.adaption_technique
|
||||
case adaption_method.lms
|
||||
weight = mu;
|
||||
grad = err(symbol) * U;
|
||||
update = grad * weight;
|
||||
|
||||
case adaption_method.nlms
|
||||
normU = (U.'*U) + eps;
|
||||
weight = mu / normU;
|
||||
grad = err(symbol) * U;
|
||||
update = grad * weight;
|
||||
|
||||
case adaption_method.rls
|
||||
denom = lambda + U.' * obj.P * U;
|
||||
k = (obj.P * U) / denom;
|
||||
update = k * err(symbol);
|
||||
end
|
||||
|
||||
if ffe_buffer_enabled
|
||||
grad_buffer = circshift(grad_buffer,1,2);
|
||||
grad_buffer(:,1) = update;
|
||||
if mod(symbol,obj.ffe_buffer_len) == 0
|
||||
obj.e = obj.e + mean(grad_buffer,2,"omitnan");
|
||||
end
|
||||
else
|
||||
obj.e = obj.e + update;
|
||||
end
|
||||
|
||||
if obj.adaption_technique == adaption_method.rls
|
||||
obj.P = (1/lambda) * (obj.P - k * (U.' * obj.P));
|
||||
end
|
||||
|
||||
if obj.mu_dc ~= 0
|
||||
if adaptive_dc_enabled
|
||||
delta_mu = gamma_dc * err(symbol) * err_prev * (U.'*U);
|
||||
obj.mu_dc = min(max(obj.mu_dc + delta_mu,mu_min),mu_max);
|
||||
err_prev = err(symbol);
|
||||
P_err = alpha*P_err + (1-alpha)*err(symbol)^2;
|
||||
mu_dc_eff = obj.mu_dc / (P_err + eps);
|
||||
else
|
||||
mu_dc_eff = obj.mu_dc;
|
||||
end
|
||||
|
||||
if dc_buffer_enabled
|
||||
e_dc_buffer = circshift(e_dc_buffer,1);
|
||||
e_dc_buffer(1) = obj.e_dc + mu_dc_eff * err(symbol);
|
||||
if mod(symbol,obj.dc_buffer_len) == 0
|
||||
obj.e_dc = median(e_dc_buffer,"omitnan");
|
||||
end
|
||||
else
|
||||
obj.e_dc = obj.e_dc + mu_dc_eff * err(symbol);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if obj.save_debug
|
||||
obj.debug_struct.error(epoch,symbol) = err(symbol) * err(symbol)';
|
||||
|
||||
if training
|
||||
obj.debug_struct.error_tr(epoch,symbol) = err(symbol) * err(symbol)';
|
||||
obj.debug_struct.update_tr(epoch,symbol) = update.'*update ./ rms(obj.e);
|
||||
end
|
||||
end
|
||||
|
||||
% obj.error(epoch,symbol) = err(symbol) * err(symbol)';
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function [y,d_hat] = applyCurrentTaps(obj,x,N)
|
||||
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
|
||||
for sample = 1 : obj.sps : N
|
||||
symbol = (sample - 1) / obj.sps + 1;
|
||||
U = x(obj.order+sample-1:-1:sample);
|
||||
y(symbol,1) = obj.e_dc + obj.e.' * U;
|
||||
[~,symbol_idx] = min(abs(y(symbol) - obj.constellation));
|
||||
d_hat(symbol,1) = obj.constellation(symbol_idx);
|
||||
end
|
||||
end
|
||||
|
||||
function N_dd = ddLength(obj,N)
|
||||
if isempty(obj.dd_len_fraction) || obj.dd_len_fraction <= 0 || obj.dd_len_fraction >= 1
|
||||
N_dd = N;
|
||||
return
|
||||
end
|
||||
|
||||
N_dd = floor(N * obj.dd_len_fraction);
|
||||
N_dd = max(obj.sps,N_dd);
|
||||
N_dd = min(N,N_dd);
|
||||
end
|
||||
|
||||
function optimizeMus(obj,x,d)
|
||||
switch obj.adaption_technique
|
||||
case adaption_method.lms
|
||||
mu_range = [1e-5, 1e-2];
|
||||
case adaption_method.nlms
|
||||
mu_range = [1e-3, 5e-1];
|
||||
case adaption_method.rls
|
||||
mu_range = [0.98, 0.99999];
|
||||
end
|
||||
mu_dc_range = [1e-5, 1e-1];
|
||||
|
||||
mu_tr_var = optimizableVariable("mu_tr",mu_range,"Transform","log");
|
||||
vars = mu_tr_var;
|
||||
if obj.dd_mode
|
||||
vars = [vars, optimizableVariable("mu_dd",mu_range,"Transform","log")];
|
||||
end
|
||||
optimize_mu_dc = obj.mu_dc ~= 0;
|
||||
if optimize_mu_dc
|
||||
vars = [vars, optimizableVariable("mu_dc",mu_dc_range,"Transform","log")];
|
||||
end
|
||||
obj.mu_optimization_iter = 0;
|
||||
obj.mu_optimization = bayesopt(@(p)obj.muObjective(p,x,d),vars, ...
|
||||
"MaxObjectiveEvaluations",10, ...
|
||||
"AcquisitionFunctionName","expected-improvement-plus", ...
|
||||
"IsObjectiveDeterministic",false, ...
|
||||
"Verbose",0, ...
|
||||
"PlotFcn",[]);
|
||||
obj.mu_tr = obj.mu_optimization.XAtMinObjective.mu_tr;
|
||||
if obj.dd_mode
|
||||
obj.mu_dd = obj.mu_optimization.XAtMinObjective.mu_dd;
|
||||
end
|
||||
if optimize_mu_dc
|
||||
obj.mu_dc = obj.mu_optimization.XAtMinObjective.mu_dc;
|
||||
end
|
||||
objective_db = 10*log10(obj.mu_optimization.MinObjective);
|
||||
if obj.dd_mode && optimize_mu_dc
|
||||
fprintf("\nFFE_DCremoval_adaptive_mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
|
||||
obj.mu_tr,obj.mu_dd,obj.mu_dc,obj.mu_optimization.MinObjective,objective_db);
|
||||
elseif obj.dd_mode
|
||||
fprintf("\nFFE_DCremoval_adaptive_mu opt done: mu_tr=%9.3e, mu_dd=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
|
||||
obj.mu_tr,obj.mu_dd,obj.mu_optimization.MinObjective,objective_db);
|
||||
elseif optimize_mu_dc
|
||||
fprintf("\nFFE_DCremoval_adaptive_mu opt done: mu_tr=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
|
||||
obj.mu_tr,obj.mu_dc,obj.mu_optimization.MinObjective,objective_db);
|
||||
else
|
||||
fprintf("\nFFE_DCremoval_adaptive_mu opt done: mu_tr=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB\n", ...
|
||||
obj.mu_tr,obj.mu_optimization.MinObjective,objective_db);
|
||||
end
|
||||
end
|
||||
|
||||
function objective = muObjective(obj,params,x,d)
|
||||
old_debug = obj.save_debug;
|
||||
old_mu_dc = obj.mu_dc;
|
||||
obj.save_debug = 1;
|
||||
if isprop(params,"mu_dc")
|
||||
obj.mu_dc = params.mu_dc;
|
||||
end
|
||||
obj.e = zeros(obj.order,1);
|
||||
obj.e_dc = 0;
|
||||
obj.P = (1/0.05) * eye(obj.order);
|
||||
obj.debug_struct = struct();
|
||||
obj.equalize(x,d,params.mu_tr,obj.epochs_tr,obj.len_tr,1,0);
|
||||
if obj.dd_mode
|
||||
obj.equalize(x,d,params.mu_dd,obj.epochs_dd,obj.ddLength(numel(x)),0,0);
|
||||
objective = mean(obj.debug_struct.error(end,:),"omitnan");
|
||||
else
|
||||
objective = mean(obj.debug_struct.error_tr(end,:),"omitnan");
|
||||
end
|
||||
if ~isfinite(objective)
|
||||
objective = inf;
|
||||
end
|
||||
objective_db = 10*log10(objective);
|
||||
obj.mu_optimization_iter = obj.mu_optimization_iter + 1;
|
||||
optimize_mu_dc = isprop(params,"mu_dc");
|
||||
if obj.dd_mode && optimize_mu_dc
|
||||
fprintf("\rFFE_DCremoval_adaptive_mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
|
||||
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,params.mu_dc,objective,objective_db);
|
||||
elseif obj.dd_mode
|
||||
fprintf("\rFFE_DCremoval_adaptive_mu opt %02d: mu_tr=%9.3e, mu_dd=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
|
||||
obj.mu_optimization_iter,params.mu_tr,params.mu_dd,objective,objective_db);
|
||||
elseif optimize_mu_dc
|
||||
fprintf("\rFFE_DCremoval_adaptive_mu opt %02d: mu_tr=%9.3e, mu_dc=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
|
||||
obj.mu_optimization_iter,params.mu_tr,params.mu_dc,objective,objective_db);
|
||||
else
|
||||
fprintf("\rFFE_DCremoval_adaptive_mu opt %02d: mu_tr=%9.3e, MSE=%9.3e, MSE_dB=%7.2f dB", ...
|
||||
obj.mu_optimization_iter,params.mu_tr,objective,objective_db);
|
||||
end
|
||||
obj.save_debug = old_debug;
|
||||
obj.mu_dc = old_mu_dc;
|
||||
end
|
||||
end
|
||||
end
|
||||
240
Classes/04_DSP/Equalizer/MPI_reduction/FFE_DCremoval_level.m
Normal file
240
Classes/04_DSP/Equalizer/MPI_reduction/FFE_DCremoval_level.m
Normal file
@@ -0,0 +1,240 @@
|
||||
classdef FFE_DCremoval_level < handle
|
||||
% Implementation of plain and simple FFE.
|
||||
% 1) Training mode (stable performance when you use NLMS)
|
||||
% 2) Decision directed mode
|
||||
|
||||
% Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0);
|
||||
|
||||
properties
|
||||
sps % usually 2
|
||||
order
|
||||
e
|
||||
error
|
||||
|
||||
len_tr
|
||||
mu_tr
|
||||
epochs_tr
|
||||
|
||||
mu_dd
|
||||
epochs_dd
|
||||
|
||||
mu_dc
|
||||
dc_buffer_len
|
||||
|
||||
constellation
|
||||
|
||||
decide
|
||||
|
||||
KF_meas_noise = 0;
|
||||
KF_process_noise = 0;
|
||||
KF_state_cov = 0;
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = FFE_DCremoval_level(options)
|
||||
arguments(Input)
|
||||
|
||||
options.sps = 2;
|
||||
options.order = 15;
|
||||
|
||||
options.len_tr = 4096;
|
||||
options.mu_tr = 0;
|
||||
options.epochs_tr = 5;
|
||||
|
||||
options.mu_dd = 1e-5;
|
||||
options.epochs_dd = 5;
|
||||
|
||||
options.mu_dc = 0.05;
|
||||
options.dc_buffer_len = 1;
|
||||
|
||||
options.decide = false;
|
||||
|
||||
end
|
||||
|
||||
assert(options.dc_buffer_len>0);
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
obj.e = zeros(obj.order,1);
|
||||
obj.error = 0;
|
||||
|
||||
obj.dc_buffer_len = floor(obj.dc_buffer_len);
|
||||
|
||||
end
|
||||
|
||||
function [X,Noi] = process(obj, X, D)
|
||||
|
||||
% actual processing of the signal (steps 1. - 3.)
|
||||
% 1 normalize RMS
|
||||
X = X.normalize("mode","rms");
|
||||
|
||||
obj.constellation = unique(D.signal);
|
||||
|
||||
% Training Mode
|
||||
training = 1;
|
||||
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training);
|
||||
|
||||
% Decision Directed Mode
|
||||
N = X.length;
|
||||
training = 0;
|
||||
[signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,N,training);
|
||||
|
||||
% Output Signal
|
||||
if obj.decide
|
||||
X.signal = decision;
|
||||
else
|
||||
X.signal = signal;
|
||||
end
|
||||
X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
|
||||
lbdesc = [num2str(obj.order),' tap FFE'];
|
||||
X = X.logbookentry(lbdesc); % append to logbook
|
||||
|
||||
Noi = X - D;
|
||||
|
||||
end
|
||||
function [y, d_hat, logs] = equalize(obj, x, d, mu_lms, epochs, N, training)
|
||||
% Equalize with Kalman-based DC removal; training epochs estimate KF noise parameters
|
||||
|
||||
arguments
|
||||
obj
|
||||
x
|
||||
d
|
||||
mu_lms % LMS step-size (or 0 for NLMS)
|
||||
epochs % number of training or DD epochs
|
||||
N % number of samples to process
|
||||
training % true => training mode (tap-training + noise estimation)
|
||||
end
|
||||
|
||||
% Zero-pad for filter memory
|
||||
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
|
||||
numSym = ceil(N/obj.sps);
|
||||
|
||||
% Pre-allocate outputs
|
||||
y = zeros(numSym,1);
|
||||
d_hat = zeros(numSym,1);
|
||||
|
||||
% --- Training: estimate noise stats and train taps ---
|
||||
if training
|
||||
% Pre-allocate error accumulator
|
||||
totalTrain = epochs * numSym;
|
||||
trainErrs = zeros(totalTrain,1);
|
||||
te_idx = 0;
|
||||
|
||||
for ep = 1:epochs
|
||||
s = 0;
|
||||
for n = 1:obj.sps:N
|
||||
s = s + 1;
|
||||
U = x(obj.order + n - 1 : -1 : n);
|
||||
|
||||
% Equalizer output (no DC correction yet)
|
||||
y(s) = obj.e.' * U;
|
||||
|
||||
% Decision based on known symbol
|
||||
[~, idx] = min(abs(d(s) - obj.constellation));
|
||||
d_hat(s) = obj.constellation(idx);
|
||||
|
||||
% Instantaneous error
|
||||
e_n = y(s) - d_hat(s);
|
||||
|
||||
% Collect error for noise estimation
|
||||
te_idx = te_idx + 1;
|
||||
trainErrs(te_idx) = e_n;
|
||||
|
||||
% Tap-weight update (LMS or NLMS)
|
||||
if mu_lms ~= 0
|
||||
obj.e = obj.e - mu_lms * e_n * U;
|
||||
else
|
||||
normU = (U.'*U) + eps;
|
||||
obj.e = obj.e - e_n * U / normU;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
% Estimate measurement noise R and process noise Q
|
||||
R_est = var(trainErrs(1:te_idx));
|
||||
Q_est = 1e-3 * R_est; % Q/R ratio = 1e-3 (tune as needed)
|
||||
|
||||
% Store into object for DD pass
|
||||
obj.KF_meas_noise = R_est;
|
||||
obj.KF_process_noise = Q_est;
|
||||
obj.KF_state_cov = 5*R_est; % or 5*R_est for a more “eager” start
|
||||
|
||||
% No Kalman in training; return
|
||||
logs = struct();
|
||||
return;
|
||||
end
|
||||
|
||||
% --- Decision-Directed with Kalman DC tracking ---
|
||||
% Initialize Kalman state
|
||||
x_est = 0;
|
||||
P = obj.KF_state_cov; % initial P (tune in obj; e.g. 1)
|
||||
|
||||
% Logging containers
|
||||
logs.y_raw = zeros(numSym,1);
|
||||
logs.y_corr = zeros(numSym,1);
|
||||
logs.err = zeros(numSym,1);
|
||||
logs.K_gain = zeros(numSym,1);
|
||||
logs.x_est = zeros(numSym,1);
|
||||
logs.P = zeros(numSym,1);
|
||||
logs.normU = zeros(numSym,1);
|
||||
logs.tap_norm = zeros(numSym,1);
|
||||
|
||||
for ep = 1:epochs
|
||||
s = 0;
|
||||
for n = 1:obj.sps:N
|
||||
s = s + 1;
|
||||
U = x(obj.order + n - 1 : -1 : n);
|
||||
|
||||
% 1) Kalman prediction
|
||||
P = P + obj.KF_process_noise;
|
||||
x_prior = x_est;
|
||||
|
||||
% 2) raw equalizer output
|
||||
y_raw = obj.e.' * U;
|
||||
logs.y_raw(s) = y_raw;
|
||||
|
||||
% 3) DC-corrected output
|
||||
y_corr = y_raw + x_prior;
|
||||
y(s) = y_corr;
|
||||
logs.y_corr(s) = y_corr;
|
||||
|
||||
% 4) decision-directed symbol
|
||||
[~, idx] = min(abs(y_corr - obj.constellation));
|
||||
d_hat(s) = obj.constellation(idx);
|
||||
|
||||
% 5) error
|
||||
e_n = y_corr - d_hat(s);
|
||||
logs.err(s) = e_n;
|
||||
|
||||
% 6) tap-weight update (LMS/NLMS)
|
||||
if mu_lms ~= 0
|
||||
obj.e = obj.e - mu_lms * e_n * U;
|
||||
else
|
||||
normU = U.' * U + eps;
|
||||
logs.normU(s) = normU;
|
||||
obj.e = obj.e - e_n * U / normU;
|
||||
end
|
||||
|
||||
logs.tap_norm(s) = norm(obj.e);
|
||||
|
||||
% 7) Kalman update
|
||||
K_gain = P / (P + obj.KF_meas_noise);
|
||||
x_est = x_prior + K_gain * (e_n - x_prior);
|
||||
P = (1 - K_gain) * P;
|
||||
|
||||
% 8) log Kalman state
|
||||
logs.K_gain(s) = K_gain;
|
||||
logs.x_est(s) = x_est;
|
||||
logs.P(s) = P;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
176
Classes/04_DSP/Equalizer/MPI_reduction/FFE_FFDCAVG.m
Normal file
176
Classes/04_DSP/Equalizer/MPI_reduction/FFE_FFDCAVG.m
Normal file
@@ -0,0 +1,176 @@
|
||||
classdef FFE_FFDCAVG < handle
|
||||
% Implementation of plain and simple FFE.
|
||||
% 1) Training mode (stable performance when you use NLMS)
|
||||
% 2) Decision directed mode
|
||||
|
||||
% Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0);
|
||||
|
||||
properties
|
||||
sps % usually 2
|
||||
order
|
||||
e
|
||||
error
|
||||
|
||||
len_tr
|
||||
mu_tr
|
||||
epochs_tr
|
||||
|
||||
mu_dd
|
||||
epochs_dd
|
||||
|
||||
mu_buff
|
||||
|
||||
constellation
|
||||
|
||||
decide
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = FFE_FFDCAVG(options)
|
||||
arguments(Input)
|
||||
|
||||
options.sps = 2;
|
||||
options.order = 15;
|
||||
|
||||
options.len_tr = 4096;
|
||||
options.mu_tr = 0;
|
||||
options.epochs_tr = 5;
|
||||
|
||||
options.mu_dd = 1e-5;
|
||||
options.epochs_dd = 5;
|
||||
|
||||
options.mu_buff = 0;
|
||||
|
||||
options.decide = false;
|
||||
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
obj.e = zeros(obj.order,1);
|
||||
obj.error = 0;
|
||||
|
||||
end
|
||||
|
||||
function [X] = process(obj, X, D)
|
||||
|
||||
% actual processing of the signal (steps 1. - 3.)
|
||||
% 1 normalize RMS
|
||||
X = X.normalize("mode","rms");
|
||||
|
||||
obj.constellation = unique(D.signal);
|
||||
|
||||
% Training Mode
|
||||
training = 1;
|
||||
showviz = 0;
|
||||
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
|
||||
|
||||
% Decision Directed Mode
|
||||
N = X.length;
|
||||
training = 0;
|
||||
showviz = 0;
|
||||
[signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,N,training,showviz);
|
||||
|
||||
% Output Signal
|
||||
if obj.decide
|
||||
X.signal = decision;
|
||||
else
|
||||
X.signal = signal;
|
||||
end
|
||||
X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
|
||||
lbdesc = [num2str(obj.order),' tap FFE'];
|
||||
X = X.logbookentry(lbdesc); % append to logbook
|
||||
|
||||
|
||||
end
|
||||
|
||||
function [y,d_hat] = equalize(obj,x,d,mio,epochs,N,training,showviz)
|
||||
|
||||
arguments
|
||||
obj
|
||||
x
|
||||
d
|
||||
mio
|
||||
epochs
|
||||
N
|
||||
training
|
||||
showviz
|
||||
end
|
||||
|
||||
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
|
||||
|
||||
|
||||
for epoch = 1 : epochs
|
||||
symbol = 0;
|
||||
|
||||
err_buffer = zeros(numel(obj.constellation),112);
|
||||
dc_err = zeros(numel(obj.constellation),1);
|
||||
dc_sto = NaN(numel(obj.constellation),N);
|
||||
|
||||
for sample = 1 : obj.sps : N
|
||||
|
||||
symbol = symbol+1;
|
||||
|
||||
U = x(obj.order+sample-1:-1:sample);
|
||||
|
||||
y(symbol,1) = obj.e.' * U; % Calculating output of LMS __ * |
|
||||
|
||||
if training
|
||||
[~,symbol_idx] = min(abs(d(symbol) - obj.constellation)); % decision for closest constellation point
|
||||
d_hat(symbol,1) = d(symbol);
|
||||
else
|
||||
always_correct_decision = 0;
|
||||
if always_correct_decision
|
||||
[~,symbol_idx] = min(abs(d(symbol) - obj.constellation)); % decision for closest constellation point
|
||||
else
|
||||
[~,symbol_idx] = min(abs(y(symbol) - obj.constellation)); % decision for closest constellation point
|
||||
end
|
||||
d_hat(symbol,1) = obj.constellation(symbol_idx);
|
||||
end
|
||||
|
||||
err(symbol) = y(symbol) - d_hat(symbol); % Instantaneous error
|
||||
|
||||
if 1
|
||||
%use buffer for dc-error
|
||||
err_buffer(symbol_idx,1) = err(symbol);
|
||||
err_buffer(symbol_idx,:) = circshift(err_buffer(symbol_idx,:),1);
|
||||
dc_sto(symbol_idx,symbol) = obj.mu_buff*mean(err_buffer(symbol_idx,:));
|
||||
y(symbol) = y(symbol) - obj.mu_buff * mean(err_buffer(symbol_idx,:));
|
||||
else
|
||||
%or use 1+alpha*D as adaptive error
|
||||
dc_err(symbol_idx) = dc_err(symbol_idx) + obj.mu_buff * err(symbol);
|
||||
dc_sto(symbol_idx,symbol) = dc_err(symbol_idx);
|
||||
y(symbol) = y(symbol) - dc_err(symbol_idx);
|
||||
end
|
||||
|
||||
if training
|
||||
[~,symbol_idx] = min(abs(d(symbol) - obj.constellation)); % decision for closest constellation point
|
||||
d_hat(symbol,1) = d(symbol);
|
||||
else
|
||||
[~,symbol_idx] = min(abs(y(symbol) - obj.constellation)); % decision for closest constellation point
|
||||
d_hat(symbol,1) = obj.constellation(symbol_idx);
|
||||
end
|
||||
|
||||
err(symbol) = y(symbol) - d_hat(symbol); % Instantaneous error
|
||||
|
||||
if mio ~= 0
|
||||
obj.e = obj.e - (mio * err(symbol) * U) ; % Weight update rule of LMS
|
||||
else
|
||||
normalizationfactor = (U.' * U);
|
||||
obj.e = obj.e - err(symbol) * U / normalizationfactor; % Weight update rule of NLMS
|
||||
end
|
||||
|
||||
|
||||
obj.error(epoch,symbol) = err(symbol) * err(symbol)'; % Instantaneous square error
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
155
Classes/04_DSP/Equalizer/MPI_reduction/FFE_adaptive_decision.m
Normal file
155
Classes/04_DSP/Equalizer/MPI_reduction/FFE_adaptive_decision.m
Normal file
@@ -0,0 +1,155 @@
|
||||
classdef FFE_adaptive_decision < handle
|
||||
% Implementation of plain and simple FFE.
|
||||
% 1) Training mode (stable performance when you use NLMS)
|
||||
% 2) Decision directed mode
|
||||
|
||||
% Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0);
|
||||
|
||||
properties
|
||||
sps % usually 2
|
||||
order
|
||||
e
|
||||
error
|
||||
|
||||
len_tr
|
||||
mu_tr
|
||||
epochs_tr
|
||||
|
||||
mu_dd
|
||||
epochs_dd
|
||||
|
||||
buffer_length
|
||||
|
||||
constellation
|
||||
|
||||
decide
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = FFE_adaptive_decision(options)
|
||||
arguments(Input)
|
||||
|
||||
options.sps = 2;
|
||||
options.order = 15;
|
||||
|
||||
options.len_tr = 4096;
|
||||
options.mu_tr = 0;
|
||||
options.epochs_tr = 5;
|
||||
|
||||
options.mu_dd = 1e-5;
|
||||
options.epochs_dd = 5;
|
||||
|
||||
options.buffer_length = 100;
|
||||
|
||||
options.decide = false;
|
||||
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
obj.e = zeros(obj.order,1);
|
||||
obj.error = 0;
|
||||
|
||||
end
|
||||
|
||||
function [X,Noi] = process(obj, X, D)
|
||||
|
||||
% actual processing of the signal (steps 1. - 3.)
|
||||
% 1 normalize RMS
|
||||
X = X.normalize("mode","rms");
|
||||
|
||||
obj.constellation = unique(D.signal);
|
||||
|
||||
% Training Mode
|
||||
training = 1;
|
||||
showviz = 0;
|
||||
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_tr,obj.len_tr,training,showviz);
|
||||
|
||||
% Decision Directed Mode
|
||||
N = X.length;
|
||||
training = 0;
|
||||
showviz = 0;
|
||||
[signal,decision]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,N,training,showviz);
|
||||
|
||||
% Output Signal
|
||||
if obj.decide
|
||||
X.signal = decision;
|
||||
else
|
||||
X.signal = signal;
|
||||
end
|
||||
|
||||
X.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
|
||||
lbdesc = [num2str(obj.order),' tap FFE'];
|
||||
X = X.logbookentry(lbdesc); % append to logbook
|
||||
|
||||
Noi = X;
|
||||
Noi = X - D;
|
||||
|
||||
end
|
||||
|
||||
function [y,d_hat] = equalize(obj,x,d,mio,epochs,N,training,showviz)
|
||||
|
||||
arguments
|
||||
obj
|
||||
x
|
||||
d
|
||||
mio
|
||||
epochs
|
||||
N
|
||||
training
|
||||
showviz
|
||||
end
|
||||
|
||||
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
|
||||
|
||||
for epoch = 1 : epochs
|
||||
symbol = 0;
|
||||
% y_buffer = zeros(numel(obj.constellation),500);
|
||||
y_buffer = repmat(obj.constellation,1,obj.buffer_length);
|
||||
for sample = 1 : obj.sps : N
|
||||
|
||||
symbol = symbol+1;
|
||||
|
||||
U = x(obj.order+sample-1:-1:sample);
|
||||
|
||||
y(symbol,1) = obj.e.' * U; % Calculating output of LMS __ * |
|
||||
|
||||
if training
|
||||
[~,symbol_idx] = min(abs(d(symbol) - obj.constellation)); % decision for closest constellation point
|
||||
d_hat(symbol,1) = d(symbol);
|
||||
else
|
||||
y_buffer(y_buffer==0) = NaN;
|
||||
adap_constellation = mean(y_buffer,2,"omitnan");
|
||||
[~,symbol_idx] = min(abs(y(symbol) - adap_constellation)); % decision for closest constellation point
|
||||
d_hat(symbol,1) = obj.constellation(symbol_idx);
|
||||
end
|
||||
|
||||
y_buffer(symbol_idx,1) = y(symbol);
|
||||
y_buffer(symbol_idx,:) = circshift(y_buffer(symbol_idx,:),1);
|
||||
|
||||
adap_constellation = mean(y_buffer,2,"omitnan");
|
||||
delta_y_y(symbol) = y(symbol) - adap_constellation(symbol_idx);
|
||||
|
||||
err(symbol) = y(symbol) - d_hat(symbol); % Instantaneous error
|
||||
|
||||
|
||||
if mio ~= 0
|
||||
obj.e = obj.e - (mio * err(symbol) * U) ; % Weight update rule of LMS
|
||||
else
|
||||
normalizationfactor = (U.' * U);
|
||||
obj.e = obj.e - err(symbol) * U / normalizationfactor; % Weight update rule of NLMS
|
||||
end
|
||||
|
||||
obj.error(epoch,symbol) = err(symbol) * err(symbol)'; % Instantaneous square error
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user