Gif stuff
nonlinear MLSE investigation trying hard to implment ML-based pre Equalization to find the branch metrics
This commit is contained in:
@@ -344,7 +344,7 @@ classdef Signal
|
||||
|
||||
arguments
|
||||
obj
|
||||
options.fignum
|
||||
options.fignum = 2025
|
||||
options.displayname = "";
|
||||
options.color = [];
|
||||
options.normalizeToNyquist = 0;
|
||||
@@ -449,21 +449,25 @@ classdef Signal
|
||||
|
||||
ylabel(ylab);
|
||||
|
||||
% --- Y-Axis scaling (auto with margin) ---
|
||||
y_min = min(p_dbm(:));
|
||||
y_max = max(p_dbm(:));
|
||||
|
||||
% Add 5% dynamic range margin on both sides
|
||||
y_range = y_max - y_min;
|
||||
if y_range == 0
|
||||
y_range = 10; % fallback if flat
|
||||
end
|
||||
|
||||
y_margin = 0.05 * y_range;
|
||||
ylim([y_min - y_margin, y_max + y_margin]);
|
||||
|
||||
% Set ticks automatically, avoid overpopulation
|
||||
try
|
||||
ylim([max(min(floor(min(p_dbm))-3, ax.YLim(1)),-40), min(max(ceil(max(p_dbm))+3, ax.YLim(2)),10)]);
|
||||
catch
|
||||
ylim([floor(min(p_dbm,[],'all'))-3, ceil(max(p_dbm,[],'all'))+3]);
|
||||
yticks(round(linspace(y_min, y_max, min(10, max(4, ceil(y_range/10))))));
|
||||
end
|
||||
|
||||
if options.normalizeTo0dB
|
||||
ylim([floor(min(p_dbm,[],'all'))-3, ceil(max(p_dbm,[],'all'))+3]);
|
||||
else
|
||||
ylim([floor(min(p_dbm,[],'all'))-3, ceil(max(p_dbm,[],'all'))+3]);
|
||||
end
|
||||
|
||||
yticks(-200:10:10);
|
||||
grid on;
|
||||
legend
|
||||
|
||||
|
||||
end
|
||||
|
||||
@@ -961,8 +965,8 @@ classdef Signal
|
||||
maxA = max(sig(100:end-100))*1.3;
|
||||
minA = min(sig(100:end-100))*1.3;
|
||||
|
||||
% maxA = 0.0015;
|
||||
% minA = 0;
|
||||
maxA = 0.0025;
|
||||
minA = 0;
|
||||
|
||||
difference= maxA-minA;
|
||||
|
||||
@@ -1012,7 +1016,7 @@ classdef Signal
|
||||
|
||||
% add information
|
||||
|
||||
if 1
|
||||
if 0
|
||||
|
||||
pwr_dbm = round(obj.power,3);
|
||||
pwr_lin = obj.power("unit",power_notation.W);
|
||||
@@ -1121,20 +1125,20 @@ classdef Signal
|
||||
|
||||
end
|
||||
|
||||
yticks(linspace(0,histpoints,6));
|
||||
y_tickstring = sprintfc('%.2f', y_tickstring);
|
||||
yticklabels(y_tickstring);
|
||||
|
||||
xticks(linspace(0,histpoints_horizontal,6))
|
||||
x_tickstring = sprintfc('%.2f', linspace(0, 2/fsym, 8) .* 1e12);
|
||||
xticklabels(x_tickstring);
|
||||
|
||||
grid off
|
||||
|
||||
end
|
||||
|
||||
|
||||
yticks(linspace(0,histpoints,6));
|
||||
y_tickstring = sprintfc('%.2f', y_tickstring);
|
||||
yticklabels(y_tickstring);
|
||||
|
||||
xticks(linspace(0,histpoints_horizontal,6))
|
||||
x_tickstring = sprintfc('%.2f', linspace(0, 2/fsym, 8) .* 1e12);
|
||||
xticklabels(x_tickstring);
|
||||
|
||||
%
|
||||
end
|
||||
|
||||
% disp('h');
|
||||
|
||||
@@ -131,7 +131,7 @@ classdef FFE < handle
|
||||
mask = ones(obj.order,1);
|
||||
maincursor_pos=ceil(length(obj.e)/2);
|
||||
always_ideal_decision = 0;
|
||||
save_debug = 1;
|
||||
save_debug = 0;
|
||||
grad =0;
|
||||
weight = 0;
|
||||
update = 0;
|
||||
|
||||
233
Classes/04_DSP/Equalizer/FFE_MLSE.m
Normal file
233
Classes/04_DSP/Equalizer/FFE_MLSE.m
Normal file
@@ -0,0 +1,233 @@
|
||||
classdef ML_MLSE < handle
|
||||
% Implementation of plain and simple FFE.
|
||||
% 1) Training mode (stable performance when you use NLMS)
|
||||
% 2) Decision directed mode
|
||||
|
||||
%LMS: mu in order of 0.0001 for acceptable convergence speed
|
||||
%NLMS: mu in order of 0.01 for acceptable convergence speed
|
||||
%RLS: mu is lambda -> 0.99 -> 1 (has a strong dependency on this! use a loop to find out best values)
|
||||
|
||||
% FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode);
|
||||
|
||||
properties
|
||||
sps % usually 2
|
||||
order
|
||||
e
|
||||
e_tr
|
||||
error
|
||||
|
||||
len_tr
|
||||
mu_tr
|
||||
epochs_tr
|
||||
|
||||
dd_mode % 1 or 0 to set DD-mode on or off
|
||||
mu_dd %weight update in dd mode
|
||||
epochs_dd
|
||||
|
||||
constellation
|
||||
|
||||
L %viterbi memory length
|
||||
|
||||
alpha
|
||||
DIR
|
||||
DIR_flip
|
||||
trellis_states
|
||||
|
||||
traceback_depth
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = ML_MLSE(options)
|
||||
arguments(Input)
|
||||
|
||||
options.sps = 2;
|
||||
options.order = 15;
|
||||
|
||||
options.len_tr = 4096;
|
||||
options.mu_tr = 0;
|
||||
options.epochs_tr = 5;
|
||||
|
||||
options.dd_mode = 1;
|
||||
options.mu_dd = 1e-5;
|
||||
options.epochs_dd = 5;
|
||||
|
||||
options.traceback_depth = 1024;
|
||||
|
||||
options.L = 1
|
||||
|
||||
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,X_viterbi] = 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);
|
||||
|
||||
if length(X)/length(D) ~= obj.sps
|
||||
warning('Signal length does not fit to reference!');
|
||||
end
|
||||
|
||||
% Training Mode
|
||||
n = obj.len_tr;
|
||||
training = 1;
|
||||
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_dd,n,training);
|
||||
obj.e_tr = obj.e;
|
||||
|
||||
% Decision Directed Mode
|
||||
n = X.length;
|
||||
training = 0;
|
||||
[y,y_vit]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,n,training);
|
||||
|
||||
X_viterbi = X;
|
||||
|
||||
X.signal = y;
|
||||
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
|
||||
|
||||
X_viterbi.signal = y_vit;
|
||||
X_viterbi.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
|
||||
lbdesc = [num2str(obj.order),'order FFE + PF + Viterbi'];
|
||||
X_viterbi = X_viterbi.logbookentry(lbdesc); % append to logbook
|
||||
|
||||
|
||||
end
|
||||
|
||||
function [y,y_vit] = equalize(obj,x,d,mu,epochs,N,training)
|
||||
% ==============================================================
|
||||
% FFE + Whitening + Viterbi Equalizer (reference implementation)
|
||||
% ==============================================================
|
||||
|
||||
% --- Input padding and preallocation
|
||||
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
|
||||
N_ = N / obj.sps;
|
||||
y = zeros(N_,1);
|
||||
y_white = zeros(N_,1);
|
||||
|
||||
for epoch = 1:epochs
|
||||
|
||||
% ==============================================================
|
||||
% INITIALIZATION (only before final epoch and detection mode)
|
||||
% ==============================================================
|
||||
if epoch == epochs && ~training
|
||||
|
||||
% --- Parameters
|
||||
S = numel(unique(d));
|
||||
L = obj.L;
|
||||
nStates = S^L;
|
||||
nFeasible = S^(L-1)*S;
|
||||
|
||||
% --- Trellis setup
|
||||
obj.DIR = arburg(y-d, L);
|
||||
obj.DIR_flip = flip(obj.DIR);
|
||||
obj.trellis_states = reshape(unique(d),1,[]);
|
||||
|
||||
pre_comb_mat = repmat(obj.trellis_states, L, 1);
|
||||
pre_comb_cell = mat2cell(pre_comb_mat, ones(1,L), size(pre_comb_mat,2));
|
||||
combs = fliplr(combvec(pre_comb_cell{:}).');
|
||||
first_sym = combs(:,1);
|
||||
last_sym = combs(:,end);
|
||||
nStates = size(combs,1);
|
||||
|
||||
noise_free_received = inf(nStates,nStates);
|
||||
valid = false(nStates);
|
||||
|
||||
for from = 1:nStates
|
||||
for to = 1:nStates
|
||||
if all(combs(to,2:end) == combs(from,1:end-1))
|
||||
noise_free_received(to,from) = ...
|
||||
dot(combs(to,:), obj.DIR_flip(end:-1:2)) + last_sym(from)*obj.DIR_flip(1);
|
||||
valid(to,from) = true;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
nf_vec = noise_free_received(valid);
|
||||
[valid_to, valid_from] = find(valid);
|
||||
from_per_to = arrayfun(@(to)find(valid(to,:)), 1:nStates, 'UniformOutput', false);
|
||||
|
||||
% --- Noise stats
|
||||
y_ideal = conv(d(:), obj.DIR(:), "same");
|
||||
sigma2 = mean(abs(y - y_ideal).^2);
|
||||
inv2s2 = 1/(2*sigma2);
|
||||
|
||||
% --- Vector initialization
|
||||
bm_vec = zeros(1,nFeasible);
|
||||
pm = zeros(nStates,1);
|
||||
pm_next = zeros(nStates,1);
|
||||
bm_fw = zeros(nStates,nStates,length(y));
|
||||
zi = zeros(max(numel(obj.DIR)-1,0),1);
|
||||
end
|
||||
|
||||
% ==============================================================
|
||||
% RUNTIME LOOP (FFE update + Viterbi detection in last epoch)
|
||||
% ==============================================================
|
||||
symbol = 0;
|
||||
for sample = 1:obj.sps:N
|
||||
symbol = symbol + 1;
|
||||
|
||||
% --- FFE output
|
||||
U = x(obj.order+sample-1:-1:sample);
|
||||
y(symbol,1) = obj.e.' * U;
|
||||
|
||||
% --- Decision
|
||||
if training
|
||||
d_hat(symbol,1) = d(symbol);
|
||||
else
|
||||
[~,symbol_idx] = min(abs(y(symbol) - obj.constellation));
|
||||
d_hat(symbol,1) = obj.constellation(symbol_idx);
|
||||
end
|
||||
|
||||
% --- LMS weight update
|
||||
err(symbol) = d_hat(symbol) - y(symbol);
|
||||
obj.e = obj.e + mu * (err(symbol) * U);
|
||||
|
||||
% --- Whitening + Viterbi (final epoch only)
|
||||
if epoch == epochs && ~training
|
||||
|
||||
[y_white(symbol), zi] = filter(obj.DIR,1,y(symbol), zi);
|
||||
|
||||
if symbol == 1
|
||||
pm = -inf(nStates,nStates);
|
||||
pm(:,1:nStates) = 0;
|
||||
else
|
||||
bm_vec = -(y_white(symbol) - nf_vec).^2 * inv2s2;
|
||||
bm_mat = -inf(nStates,nStates);
|
||||
bm_mat(valid) = bm_vec;
|
||||
|
||||
pm_new = pm + bm_mat;
|
||||
[pm_survive(:,symbol), pm_survivor_fw_idx(:,symbol)] = max(pm_new,[],2);
|
||||
pm = repmat(pm_survive(:,symbol).', nStates,1);
|
||||
end
|
||||
|
||||
% --- Traceback
|
||||
if mod(symbol,obj.traceback_depth) == 0
|
||||
[~,viterbi_path(symbol)] = max(pm_survive(:,symbol));
|
||||
for n = symbol:-1:symbol-obj.traceback_depth+2
|
||||
viterbi_path(n-1) = pm_survivor_fw_idx(viterbi_path(n),n);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
% --- Final reconstruction
|
||||
if epoch == epochs && ~training
|
||||
y_vit = first_sym(viterbi_path);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
246
Classes/04_DSP/Equalizer/ML_MLSE.m
Normal file
246
Classes/04_DSP/Equalizer/ML_MLSE.m
Normal file
@@ -0,0 +1,246 @@
|
||||
classdef ML_MLSE < handle
|
||||
% Implementation of plain and simple FFE.
|
||||
% 1) Training mode (stable performance when you use NLMS)
|
||||
% 2) Decision directed mode
|
||||
|
||||
%LMS: mu in order of 0.0001 for acceptable convergence speed
|
||||
%NLMS: mu in order of 0.01 for acceptable convergence speed
|
||||
%RLS: mu is lambda -> 0.99 -> 1 (has a strong dependency on this! use a loop to find out best values)
|
||||
|
||||
% FFE("epochs_tr",5,"epochs_dd",2,"len_tr",2^13,"mu_dd",mu_dd,"mu_tr",mu_tr,"order",25,"sps",2,"decide",0, "adaption",adaption_method(adaption),"dd_mode",use_dd_mode);
|
||||
|
||||
properties
|
||||
sps % usually 2
|
||||
order
|
||||
e
|
||||
e_tr
|
||||
error
|
||||
|
||||
len_tr
|
||||
mu_tr
|
||||
epochs_tr
|
||||
|
||||
dd_mode % 1 or 0 to set DD-mode on or off
|
||||
mu_dd %weight update in dd mode
|
||||
epochs_dd
|
||||
|
||||
constellation
|
||||
|
||||
L %viterbi memory length
|
||||
|
||||
alpha
|
||||
DIR
|
||||
DIR_flip
|
||||
trellis_states
|
||||
|
||||
traceback_depth
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = ML_MLSE(options)
|
||||
arguments(Input)
|
||||
|
||||
options.sps = 2;
|
||||
options.order = 15;
|
||||
|
||||
options.len_tr = 4096;
|
||||
options.mu_tr = 0;
|
||||
options.epochs_tr = 5;
|
||||
|
||||
options.dd_mode = 1;
|
||||
options.mu_dd = 1e-5;
|
||||
options.epochs_dd = 5;
|
||||
|
||||
options.traceback_depth = 1024;
|
||||
|
||||
options.L = 1
|
||||
|
||||
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,X_viterbi] = 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);
|
||||
|
||||
if length(X)/length(D) ~= obj.sps
|
||||
warning('Signal length does not fit to reference!');
|
||||
end
|
||||
|
||||
% Training Mode
|
||||
n = obj.len_tr;
|
||||
training = 1;
|
||||
obj.equalize(X.signal, D.signal,obj.mu_tr,obj.epochs_dd,n,training);
|
||||
obj.e_tr = obj.e;
|
||||
|
||||
% Decision Directed Mode
|
||||
n = X.length;
|
||||
training = 0;
|
||||
[y,y_vit]=obj.equalize(X.signal, D.signal,obj.mu_dd,obj.epochs_dd,n,training);
|
||||
|
||||
X_viterbi = X;
|
||||
|
||||
X.signal = y;
|
||||
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
|
||||
|
||||
X_viterbi.signal = y_vit;
|
||||
X_viterbi.fs = D.fs; %change sampling frequency of outgoing signal from fdac e.g. 2 sps to symbol spaced = fsym
|
||||
lbdesc = [num2str(obj.order),'order FFE + PF + Viterbi'];
|
||||
X_viterbi = X_viterbi.logbookentry(lbdesc); % append to logbook
|
||||
|
||||
|
||||
end
|
||||
|
||||
function [y,y_vit] = equalize(obj,x,d,mu,epochs,N,training)
|
||||
% ==============================================================
|
||||
% FFE + Whitening + ML-Based Branch Metric Estimation + Viterbi
|
||||
% ==============================================================
|
||||
|
||||
% --- Input padding and preallocation
|
||||
x = [zeros(floor(obj.order/2),1); x; zeros(obj.order,1)];
|
||||
N_ = N / obj.sps;
|
||||
y = zeros(N_,1);
|
||||
y_white = zeros(N_,1);
|
||||
|
||||
for epoch = 1:epochs
|
||||
|
||||
% ==============================================================
|
||||
% INITIALIZATION (only before final epoch and detection mode)
|
||||
% ==============================================================
|
||||
if epoch == epochs && ~training
|
||||
|
||||
% --- Parameters
|
||||
S = numel(unique(d)); % alphabet size
|
||||
L = obj.L; % MLSE memory
|
||||
Nf = L; % filter length
|
||||
Delta = ceil(L/2); % delay parameter
|
||||
nStates = S^L;
|
||||
nFeasible = S^(L-1)*S;
|
||||
|
||||
% --- Trellis mapping
|
||||
obj.DIR = arburg(y-d, L);
|
||||
obj.DIR_flip = flip(obj.DIR);
|
||||
obj.trellis_states = reshape(unique(d),1,[]);
|
||||
pre_comb_mat = repmat(obj.trellis_states, L, 1);
|
||||
pre_comb_cell = mat2cell(pre_comb_mat, ones(1,L), size(pre_comb_mat,2));
|
||||
combs = fliplr(combvec(pre_comb_cell{:}).');
|
||||
first_sym = combs(:,1);
|
||||
last_sym = combs(:,end);
|
||||
nStates = size(combs,1);
|
||||
|
||||
% --- Valid transitions
|
||||
valid = false(nStates);
|
||||
for from = 1:nStates
|
||||
for to = 1:nStates
|
||||
if all(combs(to,2:end) == combs(from,1:end-1))
|
||||
valid(to,from) = true;
|
||||
end
|
||||
end
|
||||
end
|
||||
[valid_to, valid_from] = find(valid);
|
||||
|
||||
% --- Noise estimation
|
||||
y_ideal = conv(d(:), obj.DIR(:), "same");
|
||||
sigma2 = mean(abs(y - y_ideal).^2);
|
||||
inv2s2 = 1/(2*sigma2);
|
||||
|
||||
% --- Allocate vectors and weights
|
||||
pm = zeros(nStates,1);
|
||||
w = zeros(Nf,nFeasible); % filter weights per transition
|
||||
b = zeros(1,nFeasible); % bias terms
|
||||
v_hat = zeros(1,nFeasible);
|
||||
v_tilde = zeros(1,nFeasible);
|
||||
bm_vec = zeros(1,nFeasible);
|
||||
zi = zeros(max(numel(obj.DIR)-1,0),1);
|
||||
end
|
||||
|
||||
% ==============================================================
|
||||
% RUNTIME LOOP
|
||||
% ==============================================================
|
||||
symbol = 0;
|
||||
for sample = 1:obj.sps:N
|
||||
symbol = symbol + 1;
|
||||
|
||||
% --- FFE output
|
||||
U = x(obj.order+sample-1:-1:sample);
|
||||
y(symbol,1) = obj.e.' * U;
|
||||
|
||||
% --- Decision / FFE adaptation
|
||||
if training
|
||||
d_hat = d(symbol);
|
||||
else
|
||||
[~,idx] = min(abs(y(symbol) - obj.constellation));
|
||||
d_hat = obj.constellation(idx);
|
||||
end
|
||||
err = d_hat - y(symbol);
|
||||
obj.e = obj.e + mu * (err * U);
|
||||
|
||||
% --- Whitening + MLSE in last epoch
|
||||
if epoch == epochs && ~training
|
||||
|
||||
[y_white(symbol), zi] = filter(obj.DIR,1,y(symbol), zi);
|
||||
k = symbol;
|
||||
|
||||
% --- Build Δ-delayed observation window y_k
|
||||
i1 = k - Nf + 1 + Delta;
|
||||
i2 = k + Delta;
|
||||
buf = y_white(max(1,i1):min(length(y_white),i2));
|
||||
padL = max(0,1 - i1);
|
||||
padR = max(0,i2 - length(y_white));
|
||||
yk = [zeros(padL,1); buf(:); zeros(padR,1)]; % Nf×1
|
||||
|
||||
% --- Predict branch metrics for all feasible transitions
|
||||
v_hat = (yk.' * w) + b; % [1×nFeasible]
|
||||
v_hat = v_hat.'; % [nFeasible×1]
|
||||
|
||||
% --- Extended path metrics
|
||||
v_tilde = pm(valid_from) + v_hat; % [nFeasible×1]
|
||||
|
||||
% --- Compute branch metrics (distance)
|
||||
bm_vec = -(y_white(k) - v_hat).^2 * inv2s2; % 1×nFeasible
|
||||
|
||||
% --- Survivor selection (vector aggregation)
|
||||
pm_new_vec = pm(valid_from) + bm_vec.'; % nFeasible×1
|
||||
pm_next = -inf(nStates,1);
|
||||
surv_idx = zeros(nStates,1);
|
||||
for t = 1:nStates
|
||||
mask = (valid_to==t);
|
||||
[pm_next(t), arg] = max(pm_new_vec(mask));
|
||||
surv_idx(t) = valid_from(find(mask,1,'first')-1+arg);
|
||||
end
|
||||
pm = pm_next;
|
||||
|
||||
% --- Traceback
|
||||
if mod(symbol,obj.traceback_depth) == 0
|
||||
[~,viterbi_path(symbol)] = max(pm);
|
||||
for n = symbol:-1:symbol-obj.traceback_depth+2
|
||||
viterbi_path(n-1) = surv_idx(viterbi_path(n));
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
% --- Output reconstructed path
|
||||
if epoch == epochs && ~training
|
||||
y_vit = first_sym(viterbi_path);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
@@ -277,14 +277,107 @@ classdef MLSE < handle
|
||||
|
||||
|
||||
if debug
|
||||
alpha_ = alpha - min(alpha) + eps;
|
||||
figure();hold on;
|
||||
n = 10;
|
||||
scatter(1:n,obj.trellis_states(repmat([1:numel(obj.trellis_states)]',1,n)),abs(alpha_(:,end-n+1:end)),'Marker','o','LineWidth',1);
|
||||
scatter(1:n,obj.trellis_states(viterbi_path(end-n+1:end)),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','green');
|
||||
% scatter(1:n,data_ref(end-n+1:end),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','red');
|
||||
yticks(obj.trellis_states);
|
||||
ylim([min(obj.trellis_states)-1 max(obj.trellis_states)+1]);
|
||||
% alpha_ = alpha - min(alpha) + eps;
|
||||
% figure();hold on;
|
||||
% n = 10;
|
||||
% scatter(1:n,obj.trellis_states(repmat([1:numel(obj.trellis_states)]',1,n)),abs(alpha_(:,end-n+1:end)),'Marker','o','LineWidth',1);
|
||||
% scatter(1:n,obj.trellis_states(viterbi_path(end-n+1:end)),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','green');
|
||||
% % scatter(1:n,data_ref(end-n+1:end),500,'Marker','x','LineWidth',1,'MarkerEdgeColor','red');
|
||||
% yticks(obj.trellis_states);
|
||||
% ylim([min(obj.trellis_states)-1 max(obj.trellis_states)+1]);
|
||||
|
||||
%% ----- Build true path from known sequence (data_ref) -----
|
||||
% Memory length used by VA (L = length(obj.DIR)-1 symbols stored in state)
|
||||
L = size(combs,2); % each row of combs is the L-tap state vector
|
||||
N = length(data_in);
|
||||
|
||||
% Map each trellis level to an index (1..M)
|
||||
[~, level_to_idx] = ismember(levels, levels); %#ok<ASGLU> % identity map
|
||||
[ok_ref, ref_idx] = ismember(data_ref(:).', levels);
|
||||
if ~all(ok_ref)
|
||||
warning('Some data_ref symbols are not in "levels". True-path build may fail.');
|
||||
end
|
||||
|
||||
% Precompute next_state(from_state, u_idx) LUT such that:
|
||||
% combs(next_state,:) == [ combs(from_state,2:end) , levels(u_idx) ]
|
||||
next_state = zeros(size(combs,1), numel(levels), 'uint32');
|
||||
for from = 1:size(combs,1)
|
||||
prefix = combs(from,2:end); % what must match in 'to' for a valid transition
|
||||
for ui = 1:numel(levels)
|
||||
target = [prefix, levels(ui)];
|
||||
% find the unique 'to' whose state vector equals target
|
||||
to = find(all(bsxfun(@eq, combs, target), 2), 1, 'first');
|
||||
if isempty(to), to = 0; end
|
||||
next_state(from, ui) = to;
|
||||
end
|
||||
end
|
||||
|
||||
% Initialize true path at n=1: pick any state with last_sym == data_ref(1)
|
||||
% Prefer one whose suffix matches the first available history if L>1.
|
||||
cand = find(last_sym == data_ref(1));
|
||||
if isempty(cand)
|
||||
% fallback: choose closest in amplitude (should not happen if levels match)
|
||||
[~,ix] = min(abs(last_sym - data_ref(1)));
|
||||
cand = ix;
|
||||
end
|
||||
true_state_path = zeros(1,N,'uint32');
|
||||
true_state_path(1) = cand(1);
|
||||
|
||||
% Propagate forward using the known inputs data_ref(n)
|
||||
for n = 2:N
|
||||
ui = ref_idx(n); % index of the actual transmitted level at time n
|
||||
from = true_state_path(n-1);
|
||||
if from==0 || ui==0
|
||||
true_state_path(n) = 0;
|
||||
else
|
||||
true_state_path(n) = next_state(from, ui);
|
||||
if true_state_path(n)==0
|
||||
% Safety fallback: if no valid transition found (should not happen)
|
||||
% choose any to-state whose vector matches shift+current symbol
|
||||
target = [combs(from,2:end), levels(ui)];
|
||||
to = find(all(bsxfun(@eq, combs, target), 2), 1, 'first');
|
||||
if isempty(to), to = from; end
|
||||
true_state_path(n) = to;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%% ----- Collect branch metrics along decoded vs. true path -----
|
||||
bm_decoded = nan(1,N);
|
||||
bm_true = nan(1,N);
|
||||
|
||||
% n=1 in your code stores pm into bm_fw(:,:,1); real BMs start at n>=2
|
||||
for n = 2:N
|
||||
% Decoded path: to = viterbi_path(n), from = survivor that fed it
|
||||
to_d = viterbi_path(n);
|
||||
from_d = pm_survivor_fw_idx(to_d, n);
|
||||
bm_decoded(n) = bm_fw(to_d, from_d, n);
|
||||
|
||||
% True path: transition true_state_path(n-1) -> true_state_path(n)
|
||||
to_t = true_state_path(n);
|
||||
from_t = true_state_path(n-1);
|
||||
if to_t>0 && from_t>0
|
||||
bm_true(n) = bm_fw(to_t, from_t, n);
|
||||
end
|
||||
end
|
||||
|
||||
% Convert to "cost" for intuitive plotting (your BM is a log-likelihood)
|
||||
cost_dec = -bm_decoded;
|
||||
cost_true= -bm_true;
|
||||
|
||||
%% ----- Plot a short window for clarity -----
|
||||
win = max(2, N-20000):N; % last 200 samples (adjust as needed)
|
||||
figure('Color','w'); hold on; grid on; box on;
|
||||
plot(win, cost_true(win), 'LineWidth',1.2, 'DisplayName','True path cost (−BM)');
|
||||
plot(win, cost_dec(win), 'LineWidth',1.2, 'DisplayName','Decoded path cost (−BM)');
|
||||
xlabel('Time index n'); ylabel('Branch cost'); title('Branch metrics along true vs decoded path');
|
||||
legend('Location','best');
|
||||
|
||||
%% ----- Optional: overlay symbol levels for the same window -----
|
||||
yyaxis right
|
||||
plot(win, data_in(win), ':', 'LineWidth',0.8, 'DisplayName','y(n)');
|
||||
ylabel('Amplitude');
|
||||
|
||||
end
|
||||
|
||||
VITERBI_ESTIMATION_SYMBOLS(1:length(data_in)) = first_sym(viterbi_path);
|
||||
@@ -564,11 +657,6 @@ classdef MLSE < handle
|
||||
|
||||
end
|
||||
|
||||
function s = logsumexp(a,dim)
|
||||
% returns log(sum(exp(a),dim)) safely
|
||||
amax = max(a,[],dim);
|
||||
s = amax + log(sum(exp(a - amax), dim));
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
146
Classes/GifWriter.m
Normal file
146
Classes/GifWriter.m
Normal file
@@ -0,0 +1,146 @@
|
||||
classdef GifWriter < handle
|
||||
%GIFWRITER Simple class to create GIFs from figures (parallel-safe)
|
||||
%
|
||||
% Example:
|
||||
% g = GifWriter('Name','mySim','Parallel',true);
|
||||
% parfor i = 1:10
|
||||
% plot(rand(10,1));
|
||||
% g.addFrame(1,i);
|
||||
% end
|
||||
% g.compile(1);
|
||||
|
||||
properties
|
||||
Name (1,:) char = 'default' % GIF base name
|
||||
DelayTime (1,1) double = 0.1 % Frame delay in seconds
|
||||
Parallel (1,1) logical = false % Enable parallel-safe mode
|
||||
BaseDir (1,:) char % Base directory for temp frames
|
||||
OutputDir (1,:) char % Final GIF output directory
|
||||
end
|
||||
|
||||
methods
|
||||
%% Constructor
|
||||
function obj = GifWriter(varargin)
|
||||
% Parse name/value pairs
|
||||
p = inputParser;
|
||||
addParameter(p, 'Name', 'default', @ischar);
|
||||
addParameter(p, 'DelayTime', 0.1, @isnumeric);
|
||||
addParameter(p, 'Parallel', false, @islogical);
|
||||
addParameter(p, 'OutputDir', fullfile(pwd, 'gif_output'), @ischar);
|
||||
parse(p, varargin{:});
|
||||
|
||||
obj.Name = p.Results.Name;
|
||||
obj.DelayTime = p.Results.DelayTime;
|
||||
obj.Parallel = p.Results.Parallel;
|
||||
obj.OutputDir = p.Results.OutputDir;
|
||||
obj.BaseDir = fullfile(obj.OutputDir, 'tmp', obj.Name);
|
||||
|
||||
if ~exist(obj.BaseDir, 'dir'), mkdir(obj.BaseDir); end
|
||||
if ~exist(obj.OutputDir, 'dir'), mkdir(obj.OutputDir); end
|
||||
end
|
||||
|
||||
%%
|
||||
function addFrame(obj, figInput, pos)
|
||||
%ADDFRAME Add a figure frame to the GIF (supports parallel mode)
|
||||
%
|
||||
% Usage:
|
||||
% obj.addFrame(figHandle)
|
||||
% obj.addFrame(figNum)
|
||||
% obj.addFrame(figHandle, pos) % parallel mode
|
||||
% obj.addFrame(figNum, pos)
|
||||
%
|
||||
% In parallel mode, 'pos' must be a unique integer (loop index).
|
||||
|
||||
if nargin < 3, pos = []; end
|
||||
|
||||
% --- Resolve figure handle ---
|
||||
if isnumeric(figInput)
|
||||
% User passed a figure number
|
||||
if ~ishandle(figInput)
|
||||
warning('GifWriter:addFrame', 'Figure %d not found.', figInput);
|
||||
return;
|
||||
end
|
||||
figHandle = figure(figInput);
|
||||
elseif isa(figInput, 'matlab.ui.Figure')
|
||||
figHandle = figInput;
|
||||
else
|
||||
error('GifWriter:addFrame:InvalidInput', ...
|
||||
'Input must be a figure handle or figure number.');
|
||||
end
|
||||
|
||||
% --- Parallel-safe frame writing ---
|
||||
if obj.Parallel
|
||||
if isempty(pos)
|
||||
error('GifWriter:ParallelMode', ...
|
||||
'In parallel mode, provide a unique ''pos'' identifier.');
|
||||
end
|
||||
|
||||
% Directory for this figure number
|
||||
frameDir = fullfile(obj.BaseDir, sprintf('fig_%d', figHandle.Number));
|
||||
if ~exist(frameDir, 'dir')
|
||||
mkdir(frameDir);
|
||||
end
|
||||
|
||||
% File path for this frame
|
||||
frameFile = fullfile(frameDir, sprintf('frame_%05d.png', pos));
|
||||
|
||||
% Export to PNG (headless-safe)
|
||||
exportgraphics(figHandle, frameFile, 'Resolution', 150);
|
||||
|
||||
else
|
||||
% --- Serial mode: append directly to GIF ---
|
||||
gifFile = fullfile(obj.OutputDir, ...
|
||||
sprintf('%s_fig_%d.gif', obj.Name, figHandle.Number));
|
||||
|
||||
% Export frame temporarily
|
||||
tmpFile = [tempname, '.png'];
|
||||
exportgraphics(figHandle, tmpFile, 'Resolution', 150);
|
||||
img = imread(tmpFile);
|
||||
delete(tmpFile);
|
||||
|
||||
% Append to GIF
|
||||
[A, map] = rgb2ind(img, 256);
|
||||
if ~isfile(gifFile)
|
||||
imwrite(A, map, gifFile, 'gif', ...
|
||||
'LoopCount', Inf, 'DelayTime', obj.DelayTime);
|
||||
else
|
||||
imwrite(A, map, gifFile, 'gif', ...
|
||||
'WriteMode', 'append', 'DelayTime', obj.DelayTime);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
%% Compile all PNGs into a GIF (and clean up)
|
||||
function compile(obj, fignum)
|
||||
figDir = fullfile(obj.BaseDir, sprintf('fig_%d', fignum));
|
||||
gifFile = fullfile(obj.OutputDir, sprintf('%s_fig_%d.gif', obj.Name, fignum));
|
||||
|
||||
frames = dir(fullfile(figDir, 'frame_*.png'));
|
||||
if isempty(frames)
|
||||
warning('GifWriter:NoFrames', 'No frames found for figure %d.', fignum);
|
||||
return;
|
||||
end
|
||||
|
||||
% Sort by frame name
|
||||
[~, idx] = sort({frames.name});
|
||||
frames = frames(idx);
|
||||
|
||||
% Combine into a GIF
|
||||
for i = 1:numel(frames)
|
||||
img = imread(fullfile(frames(i).folder, frames(i).name));
|
||||
[A, map] = rgb2ind(img, 256);
|
||||
if i == 1
|
||||
imwrite(A, map, gifFile, 'gif', ...
|
||||
'LoopCount', Inf, 'DelayTime', obj.DelayTime);
|
||||
else
|
||||
imwrite(A, map, gifFile, 'gif', ...
|
||||
'WriteMode', 'append', 'DelayTime', obj.DelayTime);
|
||||
end
|
||||
end
|
||||
|
||||
% Clean up temporary frames
|
||||
rmdir(figDir, 's');
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user