228 lines
8.6 KiB
Matlab
228 lines
8.6 KiB
Matlab
classdef MLSE
|
|
%MLSE calculates the most probable sequence for an input signal with given/ known channel impulse response of any length
|
|
|
|
properties(Access=public)
|
|
M %PAM-M
|
|
DIR
|
|
trellis_states
|
|
duobinary_output
|
|
end
|
|
|
|
methods (Access=public)
|
|
|
|
function obj = MLSE(options)
|
|
%NAME Construct an instance of this class
|
|
% Detailed explanation goes here
|
|
|
|
arguments
|
|
options.M double = 4;
|
|
options.DIR double = [1];
|
|
options.trellis_states double = [-3 -1 1 3];
|
|
options.duobinary_output logical = false;
|
|
|
|
end
|
|
|
|
%
|
|
fn = fieldnames(options);
|
|
for n = 1:numel(fn)
|
|
try
|
|
obj.(fn{n}) = options.(fn{n});
|
|
end
|
|
end
|
|
|
|
% do more stuff
|
|
|
|
end
|
|
|
|
function signalclass = process(obj,signalclass)
|
|
|
|
data_in = signalclass.signal;
|
|
|
|
data_out = obj.process_(data_in);
|
|
|
|
signalclass.signal = data_out;
|
|
|
|
end
|
|
|
|
function data_out = process_(obj,data_in)
|
|
|
|
|
|
% remove unnecessary zeros at start of impulse response to keep
|
|
% number of trellis states minimal
|
|
DIR_nonzero = find(obj.DIR ~= 0);
|
|
if DIR_nonzero(1) > 1
|
|
obj.DIR(1:DIR_nonzero(1)-1) = [];
|
|
end
|
|
|
|
if isscalar(obj.DIR)
|
|
obj.DIR = [0 obj.DIR];
|
|
end
|
|
|
|
obj.DIR = flip(obj.DIR);
|
|
|
|
% RMS normalization of input data
|
|
data_in = data_in ./ rms(data_in);
|
|
|
|
% seems to be the only way to use combvec for a flexible amount
|
|
% of vectors. 'combs' contains all trellis states
|
|
pre_comb_mat = repmat(obj.trellis_states,length(obj.DIR)-1,1);
|
|
pre_comb_cell = mat2cell(pre_comb_mat,ones(1,size(pre_comb_mat,1)),size(pre_comb_mat,2));
|
|
combs = fliplr(combvec(pre_comb_cell{:}).');
|
|
|
|
% Save first and last symbol of each state
|
|
first_sym = combs(:,1);
|
|
last_sym = combs(:,end);
|
|
states = sum(combs,2);
|
|
|
|
% Calculate all possible input symbols for the desired impulse
|
|
% response. Row number is the index of the previous state,
|
|
% column number is the index of the next state
|
|
noise_free_received = zeros(length(states),length(states));
|
|
count_row = 1;
|
|
count_col = 1;
|
|
for l1 = 1:length(states)
|
|
for l2 = 1:length(states)
|
|
if sum(combs(l2,2:end) == combs(l1,1:end-1)) == size(combs,2)-1
|
|
noise_free_received(count_row,count_col) = sum(combs(l2,:).*obj.DIR(end:-1:2)) + last_sym(l1)*obj.DIR(1);
|
|
else
|
|
noise_free_received(count_row,count_col) = inf;
|
|
end
|
|
count_row = count_row + 1;
|
|
end
|
|
count_col = count_col + 1;
|
|
count_row = 1;
|
|
end
|
|
|
|
% match amplitude levels of input signal to those of the calculated ideal symbols
|
|
% i.e. match the rms values of data_in to noise_free_received
|
|
if isreal(data_in)
|
|
if obj.M == round(obj.M)
|
|
data_in = data_in * rms(noise_free_received(noise_free_received ~= inf),'all','omitnan');
|
|
end
|
|
end
|
|
|
|
|
|
|
|
%
|
|
% %% Optimized
|
|
% % Preallocate and initialize variables
|
|
% data_out = NaN(size(data_in)); % output vector
|
|
% sum_path_metrics_res = zeros(length(states), length(data_in));
|
|
% path_idx = zeros(length(states), length(data_in));
|
|
%
|
|
% % Precompute repmat size
|
|
% num_states = length(states);
|
|
% num_signals = numel(noise_free_received);
|
|
%
|
|
% % First trellis path (initialize)
|
|
% sum_path_metrics = zeros(num_states, num_states);
|
|
% path_metrics = (abs(data_in(1) - noise_free_received)).^2; % Use broadcasting instead of repmat
|
|
% sum_path_metrics = sum_path_metrics + path_metrics; % Compute initial path metrics
|
|
%
|
|
% [sum_path_metrics_res(:,1), path_idx(:,1)] = min(sum_path_metrics, [], 2); % Best path for first step
|
|
%
|
|
% % Preallocate path_metrics and sum_path_metrics to avoid reallocating in each loop
|
|
% path_metrics = zeros(num_states, num_signals);
|
|
%
|
|
% % Loop over remaining trellis paths
|
|
% for n = 2:length(data_in)
|
|
% % Avoid reallocation of sum_path_metrics, reuse the same matrix and update
|
|
% previous_sum_path_metrics = sum_path_metrics_res(:,n-1).'; % Transpose once for broadcasting
|
|
% sum_path_metrics = repmat(previous_sum_path_metrics, num_states, 1); % Avoid dynamic resizing
|
|
%
|
|
% % Calculate path metrics using broadcasting
|
|
% path_metrics = (abs(data_in(n) - noise_free_received)).^2; % Avoid repmat
|
|
%
|
|
% % Update sum path metrics
|
|
% sum_path_metrics = sum_path_metrics + path_metrics;
|
|
%
|
|
% % Find the best path for each state and store results
|
|
% [sum_path_metrics_res(:,n), path_idx(:,n)] = min(sum_path_metrics, [], 2);
|
|
% end
|
|
%
|
|
% %% Traceback
|
|
% ideal_path = NaN(1, length(data_in)+1); % Preallocate ideal path
|
|
% [~, ideal_path(length(data_in)+1)] = min(sum_path_metrics_res(:,length(data_in))); % Start from final state
|
|
%
|
|
% % Trace back through trellis
|
|
% for h = length(data_in):-1:1
|
|
% ideal_path(h) = path_idx(ideal_path(h+1),h); % Follow the best path back
|
|
% end
|
|
%
|
|
% % Extract the output indices
|
|
% idx_out = ideal_path(2:end);
|
|
|
|
|
|
|
|
|
|
% OLD
|
|
% initilaize the output vector
|
|
data_out = NaN(size(data_in));
|
|
|
|
sum_path_metrics = zeros(length(states),length(states));
|
|
|
|
% first trellis path
|
|
% euclidian distance as path metric
|
|
path_metrics = (abs(repmat(data_in(1),size(noise_free_received)) - noise_free_received)).^2;
|
|
sum_path_metrics = sum_path_metrics + path_metrics; % calculation of all possible sum path metrics
|
|
[sum_path_metrics_res(:,1),path_idx(:,1)] = min(sum_path_metrics,[],2); % find the best path to each state, store sum path metric and predecessor for each state
|
|
|
|
% remaining trellis paths
|
|
for n = 2:length(data_in)
|
|
sum_path_metrics = repmat(sum_path_metrics_res(:,n-1).',length(states),1);
|
|
% path_metrics = (repmat(data_in(n),size(noise_free_received)) - noise_free_received).^2;%.*prob_mat;
|
|
path_metrics = (abs(repmat(data_in(n),size(noise_free_received)) - noise_free_received)).^2;
|
|
sum_path_metrics = sum_path_metrics + path_metrics;
|
|
[sum_path_metrics_res(:,n),path_idx(:,n)] = min(sum_path_metrics,[],2);
|
|
end
|
|
|
|
%% trace back
|
|
ideal_path = NaN(1,length(data_in)+1);
|
|
% find ideal trellis path by going through the trellis
|
|
% backwards
|
|
[~,ideal_path(length(data_in)+1)] = min(sum_path_metrics_res(:,length(data_in)));
|
|
|
|
% starting with the state that has the lowest sum path
|
|
% metric, follow the stored information about the
|
|
% predecessor
|
|
for h = length(data_in):-1:1
|
|
ideal_path(h) = path_idx(ideal_path(h+1),h);
|
|
end
|
|
|
|
idx_out = ideal_path(2:length(data_in)+1);
|
|
|
|
|
|
|
|
|
|
|
|
if obj.duobinary_output
|
|
%use duobinary encoder, output is already scaled inside this
|
|
%one
|
|
data_out = Duobinary().encode(first_sym(idx_out));
|
|
|
|
else
|
|
%
|
|
data_out(1:length(data_in)) = first_sym(idx_out);
|
|
% scale to rms = 1 using the standard sqrt() expressions
|
|
if obj.M == 4
|
|
data_out = data_out./sqrt(5);
|
|
elseif obj.M == 6
|
|
data_out = data_out./sqrt(10);
|
|
elseif obj.M == 8
|
|
data_out = data_out./sqrt(21);
|
|
end
|
|
|
|
end
|
|
|
|
|
|
end
|
|
end
|
|
|
|
methods (Access=private)
|
|
% Cant be seen from outside! So put all your functions here that can/
|
|
% shall not be called from outside
|
|
|
|
|
|
end
|
|
end
|