Add new functions
This commit is contained in:
177
Classes/04_DSP/Sequence Detection/MLSE_viterbi.m
Normal file
177
Classes/04_DSP/Sequence Detection/MLSE_viterbi.m
Normal file
@@ -0,0 +1,177 @@
|
||||
classdef MLSE_viterbi < handle
|
||||
%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_viterbi(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
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%% WORKING
|
||||
% impulse respnse i.e. [0.5, 1.0000]
|
||||
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 == branch metrics
|
||||
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
|
||||
|
||||
% 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);
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%% WORKING
|
||||
|
||||
|
||||
|
||||
|
||||
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
|
||||
214
Classes/04_DSP/TransmissionPerformance.m
Normal file
214
Classes/04_DSP/TransmissionPerformance.m
Normal file
@@ -0,0 +1,214 @@
|
||||
classdef TransmissionPerformance
|
||||
% TransmissionPerformance
|
||||
%
|
||||
% This class calculates the best possible net data rate (in bits/s)
|
||||
% from a given gross rate (bits/s) and a measured channel quality
|
||||
% parameter (either bit error ratio (BER) or NGMI). The class uses
|
||||
% lookup tables for three different FEC modes:
|
||||
%
|
||||
% 1. SD+HD concatenated (uses overall code rate and an NGMI threshold)
|
||||
% 2. SD+HD concatenated with punctured LDPC (uses overall code rate and an NGMI threshold)
|
||||
% 3. HD-only (uses a code rate and a BER threshold)
|
||||
%
|
||||
% The basic algorithm is as follows:
|
||||
% 1. Given a measured quality (NGMI or BER) for each measurement, find in
|
||||
% the table the first (i.e. “best”) entry that is satisfied by the
|
||||
% measured value. For NGMI the condition is measured NGMI >= threshold,
|
||||
% while for BER the condition is measured BER <= threshold.
|
||||
% 2. Use the corresponding overall code rate to compute:
|
||||
%
|
||||
% net rate = gross rate * code rate.
|
||||
%
|
||||
% USAGE EXAMPLE (array inputs):
|
||||
%
|
||||
% tp = TransmissionPerformance;
|
||||
% % Suppose we have 3 measurements:
|
||||
% % Gross rates: 10, 15, and 20 Gbps
|
||||
% % Measured NGMI: 0.92, 0.88, and 0.85
|
||||
% % Measured BER: 1e-4, 7e-3, and 2e-2
|
||||
% netrates = tp.calculatenetrate([10e9 15e9 20e9], ...
|
||||
% 'NGMI', [0.92 0.88 0.85], ...
|
||||
% 'BER', [1e-4 7e-3 2e-2]);
|
||||
%
|
||||
% The returned structure netrates will contain the net rates (and the
|
||||
% used code rates and thresholds) for each mode in vector form.
|
||||
|
||||
properties (Constant)
|
||||
%% Lookup Table for SD+HD concatenated (using NGMI)
|
||||
% Overall code rate and NGMI threshold for each row.
|
||||
OVERALLCODERATES_SDHD = [0.7519, 0.7602, 0.7684, 0.7766, 0.7850, ...
|
||||
0.7932, 0.8014, 0.8098, 0.8180, 0.8262, ...
|
||||
0.8345, 0.8428, 0.8510, 0.8593, 0.8676, ...
|
||||
0.8733, 0.8790, 0.8848, 0.8905, 0.8962, ...
|
||||
0.9019, 0.9077, 0.9134, 0.9191, 0.9248, ...
|
||||
0.9306, 0.9363, 0.9420, 0.9477, 0.9535];
|
||||
NGMITHRESHOLDS_SDHD = [0.8116, 0.8167, 0.8241, 0.8317, 0.8401, ...
|
||||
0.8459, 0.8512, 0.8574, 0.8685, 0.8746, ...
|
||||
0.8829, 0.8892, 0.8958, 0.9022, 0.9090,...
|
||||
0.9150, 0.9210, 0.9270, 0.9330, 0.9390, ...
|
||||
0.9450, 0.9510, 0.9570, 0.9630, 0.9690, ...
|
||||
0.9750, 0.9810, 0.9870, 0.9930, 0.9990];
|
||||
|
||||
ISPUNCT_LDPI = [zeros(1,15),ones(1,15)];
|
||||
|
||||
%% Lookup Table for HD-only mode (using BER)
|
||||
% For HD-only, the table gives the code rate and the maximum acceptable BER.
|
||||
CODE_RATES_HD = [0.7500, 0.8000, 0.8123, 0.8333, 0.8571, ...
|
||||
0.8750, 0.8889, 0.9000, 0.9091, 0.9167, 0.9412];
|
||||
BERTHRESHOLDS_HD = [2.12e-2, 1.76e-2, 1.62e-2, 1.44e-2, 1.25e-2, ...
|
||||
1.03e-2, 9.29e-3, 8.33e-3, 7.54e-3, 7.04e-3, 4.70e-3];
|
||||
|
||||
%% LUT for KP4-FEC and Inner Code https://grouper.ieee.org/groups/802/3/dj/public/23_03/patra_3dj_01b_2303.pdf
|
||||
CODE_RATE_KP4_AND_INNER = [0.885799];
|
||||
BERTHRESHOLDS_KP4_AND_INNER = 4.85e-3;
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
methods
|
||||
function netrates = calculateNetRate(obj, grossRate, varargin)
|
||||
% calculatenetrate Calculate the net data rate(s) from measured data.
|
||||
%
|
||||
% netrates = tp.calculatenetrate(grossRate, 'BER', berValue, 'NGMI', ngmiValue)
|
||||
%
|
||||
% INPUTS:
|
||||
% grossRate - (scalar or vector) gross data rate(s) [bits/s]
|
||||
%
|
||||
% Optional name/value pairs:
|
||||
% 'BER' - (scalar or vector) measured bit error ratio(s)
|
||||
% 'NGMI' - (scalar or vector) measured NGMI value(s)
|
||||
%
|
||||
% At least one of 'BER' or 'NGMI' must be provided.
|
||||
%
|
||||
% OUTPUT:
|
||||
% netrates - a structure with the following fields (if available):
|
||||
% .SDHD - for SD+HD concatenated (using NGMI)
|
||||
% .SDHD_LDPC - for SD+HD with punctured LDPC (using NGMI)
|
||||
% .HD - for HD-only (using BER)
|
||||
%
|
||||
% Each sub-structure contains fields:
|
||||
% .CodeRate - the chosen overall code rate from the table (vector)
|
||||
% .Threshold - the threshold value used from the table (vector)
|
||||
% .<BER/NGMI> - net rate computed as grossRate * CodeRate (vector)
|
||||
|
||||
% Parse inputs.
|
||||
p = inputParser;
|
||||
addRequired(p, 'grossRate', @(x) isnumeric(x));
|
||||
addParameter(p, 'BER', [], @(x) isnumeric(x));
|
||||
addParameter(p, 'NGMI', [], @(x) isnumeric(x));
|
||||
parse(p, grossRate, varargin{:});
|
||||
|
||||
ber = p.Results.BER;
|
||||
ngmi = p.Results.NGMI;
|
||||
|
||||
if isempty(ber) && isempty(ngmi)
|
||||
error('At least one of ''BER'' or ''NGMI'' must be provided.');
|
||||
end
|
||||
|
||||
% Determine the number of measurements.
|
||||
numMeasurements = max([numel(grossRate), numel(ngmi), numel(ber)]);
|
||||
|
||||
% Broadcast scalars if needed.
|
||||
if isscalar(grossRate) && numMeasurements > 1
|
||||
grossRate = repmat(grossRate, 1, numMeasurements);
|
||||
elseif numel(grossRate) ~= numMeasurements
|
||||
error('grossRate must be scalar or have %d elements.', numMeasurements);
|
||||
end
|
||||
|
||||
if ~isempty(ngmi)
|
||||
if isscalar(ngmi) && numMeasurements > 1
|
||||
ngmi = repmat(ngmi, 1, numMeasurements);
|
||||
elseif numel(ngmi) ~= numMeasurements
|
||||
error('NGMI must be scalar or have %d elements.', numMeasurements);
|
||||
end
|
||||
end
|
||||
|
||||
if ~isempty(ber)
|
||||
if isscalar(ber) && numMeasurements > 1
|
||||
ber = repmat(ber, 1, numMeasurements);
|
||||
elseif numel(ber) ~= numMeasurements
|
||||
error('BER must be scalar or have %d elements.', numMeasurements);
|
||||
end
|
||||
end
|
||||
|
||||
% Initialize the output structure.
|
||||
netrates = struct;
|
||||
if ~isempty(ngmi)
|
||||
netrates.SDHD.GrossRate = NaN(1, numMeasurements);
|
||||
netrates.SDHD.NetRate = NaN(1, numMeasurements);
|
||||
netrates.SDHD.punctLDPI = NaN(1, numMeasurements);
|
||||
netrates.SDHD.CodeRate = NaN(1, numMeasurements);
|
||||
netrates.SDHD.Threshold = NaN(1, numMeasurements);
|
||||
end
|
||||
if ~isempty(ber)
|
||||
netrates.HD.GrossRate = NaN(1, numMeasurements);
|
||||
netrates.HD.NetRate = NaN(1, numMeasurements);
|
||||
netrates.HD.CodeRate = NaN(1, numMeasurements);
|
||||
netrates.HD.Threshold = NaN(1, numMeasurements);
|
||||
|
||||
netrates.KP4_hamming.GrossRate = NaN(1, numMeasurements);
|
||||
netrates.KP4_hamming.NetRate = NaN(1, numMeasurements);
|
||||
netrates.KP4_hamming.CodeRate = NaN(1, numMeasurements);
|
||||
netrates.KP4_hamming.Threshold = NaN(1, numMeasurements);
|
||||
end
|
||||
|
||||
% Process each measurement individually.
|
||||
for i = 1:numMeasurements
|
||||
|
||||
% --- NGMI-based modes ---
|
||||
if ~isempty(ngmi)
|
||||
% SD+HD concatenated mode.
|
||||
idx = find(obj.NGMITHRESHOLDS_SDHD <= ngmi(i), 1, 'last');
|
||||
if ~isempty(idx)
|
||||
codeRate = obj.OVERALLCODERATES_SDHD(idx);
|
||||
netrates.SDHD.NetRate(i) = grossRate(i) * codeRate;
|
||||
netrates.SDHD.GrossRate(i) = grossRate(i) ;
|
||||
netrates.SDHD.CodeRate(i) = codeRate;
|
||||
netrates.SDHD.Threshold(i) = obj.NGMITHRESHOLDS_SDHD(idx);
|
||||
netrates.SDHD.punctLDPI(i) = obj.ISPUNCT_LDPI(idx);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
% --- BER-based mode (HD-only) ---
|
||||
if ~isempty(ber)
|
||||
% Loop through the BER thresholds from the best (highest code rate)
|
||||
% to the worst until the measured BER is acceptable.
|
||||
idxBER = [];
|
||||
for j = length(obj.BERTHRESHOLDS_HD):-1:1
|
||||
if ber(i) <= obj.BERTHRESHOLDS_HD(j)
|
||||
idxBER = j;
|
||||
break;
|
||||
end
|
||||
end
|
||||
if ~isempty(idxBER)
|
||||
codeRate = obj.CODE_RATES_HD(idxBER);
|
||||
netrates.HD.NetRate(i) = grossRate(i) * codeRate;
|
||||
netrates.HD.GrossRate(i) = grossRate(i) ;
|
||||
netrates.HD.CodeRate(i) = codeRate;
|
||||
netrates.HD.Threshold(i) = obj.BERTHRESHOLDS_HD(idxBER);
|
||||
end
|
||||
idxBER = [];
|
||||
for j = length(obj.BERTHRESHOLDS_KP4_AND_INNER):-1:1
|
||||
if ber(i) <= obj.BERTHRESHOLDS_KP4_AND_INNER(j)
|
||||
idxBER = j;
|
||||
break;
|
||||
end
|
||||
end
|
||||
if ~isempty(idxBER)
|
||||
codeRate = obj.CODE_RATE_KP4_AND_INNER(idxBER);
|
||||
netrates.KP4_hamming.NetRate(i) = grossRate(i) * codeRate;
|
||||
netrates.KP4_hamming.GrossRate(i) = grossRate(i) ;
|
||||
netrates.KP4_hamming.CodeRate(i) = codeRate;
|
||||
netrates.KP4_hamming.Threshold(i) = obj.BERTHRESHOLDS_KP4_AND_INNER(idxBER);
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
76
Classes/DataBaseHandler/copyStylingFrom.m
Normal file
76
Classes/DataBaseHandler/copyStylingFrom.m
Normal file
@@ -0,0 +1,76 @@
|
||||
function copyStylingFrom(figNumSource, figNumTgt)
|
||||
% Get handles to the source and target figures
|
||||
sourceFig = figure(figNumSource);
|
||||
targetFig = figure(figNumTgt);
|
||||
|
||||
% Get axes of source and target figures
|
||||
sourceAxes = findall(sourceFig, 'type', 'axes');
|
||||
targetAxes = findall(targetFig, 'type', 'axes');
|
||||
|
||||
% Ensure the number of axes match
|
||||
if length(sourceAxes) ~= length(targetAxes)
|
||||
error('Number of axes in source and target figures must be the same.');
|
||||
end
|
||||
|
||||
% Loop through each pair of axes and copy styling properties
|
||||
for i = 1:length(sourceAxes)
|
||||
copyAxesProperties(sourceAxes(i), targetAxes(i));
|
||||
end
|
||||
|
||||
% Apply general figure properties if desired
|
||||
targetFig.Color = sourceFig.Color; % Background color
|
||||
end
|
||||
|
||||
function copyAxesProperties(sourceAx, targetAx)
|
||||
% List of properties to copy from source to target axes
|
||||
propsToCopy = {'XColor', 'YColor', 'ZColor', 'FontSize', 'FontName', ...
|
||||
'GridColor', 'GridLineStyle', 'MinorGridColor', 'Box', ...
|
||||
'XGrid', 'YGrid', 'ZGrid', 'XMinorGrid', 'YMinorGrid', 'ZMinorGrid', ...
|
||||
'LineWidth', 'TitleFontSizeMultiplier', 'LabelFontSizeMultiplier'};
|
||||
|
||||
% Copy properties from source to target
|
||||
for i = 1:length(propsToCopy)
|
||||
try
|
||||
targetAx.(propsToCopy{i}) = sourceAx.(propsToCopy{i});
|
||||
catch
|
||||
% Skip property if it doesn't exist or can't be copied
|
||||
end
|
||||
end
|
||||
|
||||
% Copy axis labels and titles
|
||||
targetAx.Title.String = sourceAx.Title.String;
|
||||
targetAx.XLabel.String = sourceAx.XLabel.String;
|
||||
targetAx.YLabel.String = sourceAx.YLabel.String;
|
||||
targetAx.ZLabel.String = sourceAx.ZLabel.String;
|
||||
|
||||
% Copy children elements like lines, patches, etc.
|
||||
sourceChildren = allchild(sourceAx);
|
||||
targetChildren = allchild(targetAx);
|
||||
|
||||
% Ensure the number of children elements match
|
||||
if length(sourceChildren) ~= length(targetChildren)
|
||||
warning('Number of elements in source and target axes differ. Styling may not be applied completely.');
|
||||
end
|
||||
|
||||
% Copy properties of children (like lines, patches, etc.), except colors and legends
|
||||
for i = 1:min(length(sourceChildren), length(targetChildren))
|
||||
copyObjectProperties(sourceChildren(i), targetChildren(i));
|
||||
end
|
||||
end
|
||||
|
||||
function copyObjectProperties(sourceObj, targetObj)
|
||||
% List of common properties to copy for plot elements (lines, patches, etc.)
|
||||
propsToCopy = {'LineStyle', 'LineWidth', 'Marker', 'MarkerSize', ...
|
||||
'MarkerEdgeColor', 'MarkerFaceColor', 'DisplayName'};
|
||||
|
||||
% Copy properties from source to target, excluding colors
|
||||
for i = 1:length(propsToCopy)
|
||||
try
|
||||
if ~contains(propsToCopy{i}, 'Color') % Skip color properties
|
||||
targetObj.(propsToCopy{i}) = sourceObj.(propsToCopy{i});
|
||||
end
|
||||
catch
|
||||
% Skip property if it doesn't exist or can't be copied
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user