plenty of new code and stuff - mostly AI slop and Dissertation. I will move to my own git now

This commit is contained in:
Silas Oettinghaus
2026-07-08 21:35:53 +02:00
parent 33ec5b3116
commit 699d146fb5
6 changed files with 2 additions and 2 deletions

View File

@@ -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

View 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

View File

@@ -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

View 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

View 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

View 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